diff --git a/making_school_asignment_app/lib/common/api/retrofit_client.dart b/making_school_asignment_app/lib/common/api/retrofit_client.dart index 3d7e1f9..5952a08 100644 --- a/making_school_asignment_app/lib/common/api/retrofit_client.dart +++ b/making_school_asignment_app/lib/common/api/retrofit_client.dart @@ -4,7 +4,9 @@ import 'package:dio/dio.dart' hide Headers; import 'package:making_school_asignment_app/common/job/marking_models/do_paper_details_param.dart'; import 'package:making_school_asignment_app/common/job/marking_models/do_paper_details_result.dart'; import 'package:making_school_asignment_app/common/job/marking_models/favor_param.dart'; +import 'package:making_school_asignment_app/common/job/marking_models/original_manuscript_handwriting_params.dart'; import 'package:making_school_asignment_app/common/job/marking_models/review_submission_params.dart'; +import 'package:making_school_asignment_app/page/home_page/children/homework_review/components/job_handwriting.dart'; import 'package:retrofit/retrofit.dart'; import 'package:making_school_asignment_app/common/job/annotated_class.dart'; import 'package:making_school_asignment_app/common/job/class_item.dart'; @@ -126,12 +128,11 @@ abstract class RetrofitClient { @POST("/api/hms/Annotate/AnnotateSubmit") Future reviewSubmission(@Body() ReviewSubmissionParams param); + // 获取原稿笔记 + @GET("/api/hms/HmsReport/GetStudentPaperHandwriting") + Future getOriginalManuscriptHandwriting(@Queries() OriginalManuscriptHandwritingParams param); + // OSS 上传key @GET("/api/infra/Oss/GetPresignedUri") Future getOssPresignedUri(@Query('key') String key); - - // OSS 图片上传 - @PUT('{theUrl}') - @Headers({'Content-Type': ''}) - Future uploadImag(@Path() String theUrl, @Part() File file); } diff --git a/making_school_asignment_app/lib/common/job/marking_models/original_manuscript_handwriting_params.dart b/making_school_asignment_app/lib/common/job/marking_models/original_manuscript_handwriting_params.dart new file mode 100644 index 0000000..c1bbfb9 --- /dev/null +++ b/making_school_asignment_app/lib/common/job/marking_models/original_manuscript_handwriting_params.dart @@ -0,0 +1,35 @@ +// 原稿笔迹参数 + +import 'package:json_annotation/json_annotation.dart'; + +part 'original_manuscript_handwriting_params.g.dart'; + +@JsonSerializable(checked: true, includeIfNull: false) +class OriginalManuscriptHandwritingParams extends Object { + @JsonKey(name: 'homeworkId') + String homeworkId; + + @JsonKey(name: 'studentId') + int studentId; + + @JsonKey(name: 'templateId') + int? templateId; + + @JsonKey(name: 'questionNo') + int? questionNo; + + @JsonKey(name: 'pageNum') + int? pageNum; + + OriginalManuscriptHandwritingParams({ + required this.homeworkId, + required this.studentId, + this.templateId, + this.pageNum, + this.questionNo, + }); + + factory OriginalManuscriptHandwritingParams.fromJson(Map srcJson) => _$OriginalManuscriptHandwritingParamsFromJson(srcJson); + + Map toJson() => _$OriginalManuscriptHandwritingParamsToJson(this); +} diff --git a/making_school_asignment_app/lib/common/utils/my_time_util.dart b/making_school_asignment_app/lib/common/utils/my_time_util.dart new file mode 100644 index 0000000..237cfbd --- /dev/null +++ b/making_school_asignment_app/lib/common/utils/my_time_util.dart @@ -0,0 +1,65 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'my_time_util.g.dart'; + +// 毫秒转小时、分钟、秒的函数 +TimeUnitModel? convertMilliseconds(int milliseconds) { + try { + int hours = milliseconds ~/ (1000 * 60 * 60); + int minutes = (milliseconds % (1000 * 60 * 60)) ~/ (1000 * 60); + int seconds = (milliseconds % (1000 * 60)) ~/ 1000; + + if ((milliseconds % 1000) > 500) seconds += 1; + + return TimeUnitModel(hours, minutes, seconds); + } catch (e) { + print('时间转换报错'); + } + return null; +} + +// 毫秒转小时、分钟、秒的函数 +TimeUnitModel? convertSeconds(int totalSeconds) { + try { + int hours = totalSeconds ~/ 3600; // 整除3600得到小时数 + int remainingSeconds = totalSeconds % 3600; // 取模3600得到剩余的秒数 + int minutes = remainingSeconds ~/ 60; // 整除60得到分钟数 + int seconds = remainingSeconds % 60; // 取模60得到最终的秒数 + + return TimeUnitModel(hours, minutes, seconds); + } catch (e) { + print('时间转换报错'); + } + return null; +} + +@JsonSerializable() +class TimeUnitModel extends Object { + int hours; + int minutes; + int seconds; + TimeUnitModel(this.hours, this.minutes, this.seconds); + + factory TimeUnitModel.fromJson(Map srcJson) => _$TimeUnitModelFromJson(srcJson); + + Map toJson() => _$TimeUnitModelToJson(this); + + @override + String toString() { + var timeStr = ''; + if (hours > 0) { + timeStr += '${hours > 9 ? hours : '0' + hours.toString()} '; + } + if (minutes > 0) { + timeStr += '${minutes > 9 ? minutes : '0' + minutes.toString()}'; + } + + if (timeStr.length > 0) { + timeStr += ':${seconds > 9 ? seconds : '0' + seconds.toString()}'; + } else { + timeStr += '00:${seconds > 9 ? seconds : '0' + seconds.toString()}'; + } + + return timeStr; + } +} diff --git a/making_school_asignment_app/lib/page/global_widget/my_text.dart b/making_school_asignment_app/lib/page/global_widget/my_text.dart index 9c95ef7..4f05931 100644 --- a/making_school_asignment_app/lib/page/global_widget/my_text.dart +++ b/making_school_asignment_app/lib/page/global_widget/my_text.dart @@ -12,6 +12,7 @@ import 'package:making_school_asignment_app/common/config/colorUtils.dart'; // 快捷Text使用 Text quickText(text, {double? size, + double? wordSpacing, Color color = CommonColors.defaultColor, TextAlign? align, FontWeight? fontWeight, @@ -28,6 +29,7 @@ Text quickText(text, fontSize: size, color: color, fontWeight: fontWeight, + wordSpacing: wordSpacing, ), ); } diff --git a/making_school_asignment_app/lib/page/home_page/children/homework_review/components/dropdown_switch_students_type.dart b/making_school_asignment_app/lib/page/home_page/children/homework_review/components/dropdown_switch_students_type.dart index 75223dc..a1dadc1 100644 --- a/making_school_asignment_app/lib/page/home_page/children/homework_review/components/dropdown_switch_students_type.dart +++ b/making_school_asignment_app/lib/page/home_page/children/homework_review/components/dropdown_switch_students_type.dart @@ -90,11 +90,42 @@ class _DropdownSwitchStudentsTypeState extends State value: sateData.value?.studentId, underline: Container(), isExpanded: true, + onTap: () { + print('数据..................'); + }, items: sateData.value?.students.map((e) { - return DropdownMenuItem(value: e.id, child: quickText(e.name, color: const Color.fromRGBO(79, 79, 79, 1), size: 14.sp)); + return DropdownMenuItem( + value: e.id, + child: Stack( + alignment: const FractionalOffset(0, 0.62), + children: [ + Container( + padding: sateData.value?.studentId != e.id && e.isPriority ? EdgeInsets.only(left: 14.w) : null, + child: quickText( + e.name, + size: 14.sp, + color: const Color.fromRGBO(79, 79, 79, 1), + ), + ), + if (e.isPriority && sateData.value?.studentId != e.id) + Stack( + alignment: const FractionalOffset(0.52, 0.24), + children: [ + Icon( + const IconData(0xe63d, fontFamily: "AlibabaIcon"), + size: 12.sp, + color: e.isPriority ? const Color.fromRGBO(76, 199, 147, 1) : const Color.fromRGBO(164, 164, 164, 1), + ), + quickText('优先', size: 4.sp, color: Colors.white), + ], + ), + ], + ), + ); }).toList(), hint: const Text('请选择学生'), // 锚点的显示文本 onChanged: (value) { + print('1111111111'); if (logic.state.param.value.studentId == value) return; logic.state.param.value.studentId = value; logic.state.param.value = DoPaperDetailsParam.fromJson(logic.state.param.value.toJson()); diff --git a/making_school_asignment_app/lib/page/home_page/children/homework_review/components/job_handwriting.dart b/making_school_asignment_app/lib/page/home_page/children/homework_review/components/job_handwriting.dart new file mode 100644 index 0000000..698dd58 --- /dev/null +++ b/making_school_asignment_app/lib/page/home_page/children/homework_review/components/job_handwriting.dart @@ -0,0 +1,63 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:making_school_asignment_app/common/config/request_config.dart'; + +part 'job_handwriting.g.dart'; + +@JsonSerializable() +class JobHandwriting extends Object { + @JsonKey(name: 'lattices') + List lattices; + + @JsonKey(name: 'paperPicture') + String paperPicture; + + @JsonKey(name: 'pageNum') + int pageNum; + + @JsonKey(name: 'pageCount') + int pageCount; + + JobHandwriting(this.lattices, this.paperPicture, this.pageNum, this.pageCount); + + factory JobHandwriting.fromJson(Map srcJson) => _$JobHandwritingFromJson(srcJson); + + Map toJson() => _$JobHandwritingToJson(this); +} + +@JsonSerializable() +class Lattices extends Object { + // 笔画 + @JsonKey(name: 'stroke') + int stroke; + + @JsonKey(name: 'x') + double x; + + @JsonKey(name: 'y') + double y; + + @JsonKey(name: 'time') + int time; + + @JsonKey(name: 'intervalTime') + int intervalTime; + + @JsonKey(name: 'initialization') + bool initialization; + + Lattices(this.stroke, this.x, this.y, this.time, [this.intervalTime = 0, this.initialization = false]); + + factory Lattices.fromJson(Map srcJson) => _$LatticesFromJson(srcJson); + + Map toJson() => _$LatticesToJson(this); + + /** + * 根据基准初始化坐标数据 + * @param double scaleRatio + */ + void toInitialization(double scaleRatio) { + x = x * scaleRatio; + y = y * scaleRatio; + initialization = true; + } +} diff --git a/making_school_asignment_app/lib/page/home_page/children/homework_review/components/original_manuscript_handwriting/answer_handwriting_view.dart b/making_school_asignment_app/lib/page/home_page/children/homework_review/components/original_manuscript_handwriting/answer_handwriting_view.dart new file mode 100644 index 0000000..f606d3e --- /dev/null +++ b/making_school_asignment_app/lib/page/home_page/children/homework_review/components/original_manuscript_handwriting/answer_handwriting_view.dart @@ -0,0 +1,969 @@ +import 'dart:async'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:get/get.dart'; +import 'package:making_school_asignment_app/common/job/marking_models/do_test_questions_image_info.dart'; +import 'package:making_school_asignment_app/common/job/marking_models/original_manuscript_handwriting_params.dart'; +import 'package:making_school_asignment_app/common/mixins/event_bus_mixin.dart'; +import 'package:making_school_asignment_app/common/mixins/request_tool_mixin.dart'; +import 'package:making_school_asignment_app/common/utils/anti_shake_throttling.dart'; +import 'package:making_school_asignment_app/common/utils/cached_network_img.dart'; +import 'package:making_school_asignment_app/common/utils/my_time_util.dart'; +import 'package:making_school_asignment_app/common/utils/toast_utils.dart'; +import 'package:making_school_asignment_app/common/utils/utils.dart'; +import 'package:making_school_asignment_app/page/global_widget/my_text.dart'; +import 'package:making_school_asignment_app/page/home_page/children/homework_review/components/job_handwriting.dart'; + +import 'configuration_files/logic.dart'; +import 'configuration_files/states.dart'; + +part 'answer_handwriting_view.g.dart'; + +/// 学生答题轨迹 +class AnswerHandwriting extends Dialog { + final String homeworkId; + final int studentId; + final int? templateId; + final int? pageNum; + final int? questionNo; + final Function closeCall; + AnswerHandwriting( + {super.key, required this.homeworkId, required this.studentId, required this.closeCall, this.templateId, this.pageNum, this.questionNo}) { + Get.find().params.value = OriginalManuscriptHandwritingParams( + homeworkId: homeworkId, + studentId: studentId, + templateId: templateId, + pageNum: pageNum, + questionNo: questionNo, + ); + } + final _handwritingLogic = Get.find(); + @override + Widget build(BuildContext context) { + return Center( + child: Container( + height: ScreenUtil().screenHeight / 1.2, + width: ScreenUtil().screenWidth - (ScreenUtil().scaleWidth < 1.5 ? 40.r : 40.r), + alignment: Alignment.center, + child: AnswerHandwritingMainBox( + handwritingLogic: _handwritingLogic, + homeworkId: homeworkId, + templateId: templateId, + studentId: studentId, + pageNum: pageNum, + questionNo: questionNo, + closeCall: closeCall, + ), + ), + ); + } +} + +Future showAnswerHandwriting( + BuildContext context, { + required String homeworkId, + required int studentId, + int? templateId, + int? pageNum, + int? questionNo, +}) async { + try { + backCall() { + Navigator.of(context).pop(); + } + + Get.put(HandwritingLogic(backCall)); + await showDialog( + context: context, + builder: (BuildContext context) => AnswerHandwriting( + homeworkId: homeworkId, + templateId: templateId, + studentId: studentId, + pageNum: pageNum, + questionNo: questionNo, + closeCall: backCall, + ), + ); + } finally { + Get.delete(); + } +} + +// 主图 +class AnswerHandwritingMainBox extends HookWidget with EventBusMixin { + AnswerHandwritingMainBox({ + required this.handwritingLogic, + required this.homeworkId, + required this.studentId, + required this.closeCall, + this.templateId, + this.pageNum, + this.questionNo, + super.key, + }); + + final HandwritingLogic handwritingLogic; + + final String homeworkId; + final int? templateId; + final int studentId; + final int? pageNum; + final int? questionNo; + final Function closeCall; + + @override + Widget build(BuildContext context) { + var _useStateModel = UseMainBoxState.use(homeworkId, studentId, pageNum, questionNo, templateId); + + useValueChanged(_useStateModel.handwritingData.value, (_, __) { + var theData = _useStateModel.handwritingData.value; + _useStateModel.pageNum.value = theData?.pageNum; + _useStateModel.pageCount.value = theData?.pageCount ?? 0; + _useStateModel.playPause.value = false; + _useStateModel.constantFastSpeed.value = false; + // Future.delayed(Duration.zero, () { + // _useStateModel.handwritingKey.currentState?.ref.read(jobHandwritingDrawingTrajectoryProvider.notifier).setVal([]); + // }); + // _useStateModel.handwritingDetail.value = _useStateModel.getHandwritingDetail(theData); + }); + + useValueChanged(_useStateModel.pageNum.value, (oldVal, __) { + if (oldVal != null && oldVal != _useStateModel.pageNum.value) { + _useStateModel.questionNo = null; + // _useStateModel.getData().catchError((e) => closeCall()); + } + }); + + JobHandwriting? data = _useStateModel.handwritingData.value; + HandwritingInfo? dataDetail = _useStateModel.handwritingDetail.value; + + // if (data == null || dataDetail == null) return Container(); + + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Stack( + alignment: const FractionalOffset(0, 0.5), + children: [ + // 图片展示主框 + LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) { + return HandwritingDrawBox( + boxWidth: constraints.maxWidth, + boxHeight: constraints.maxHeight, + key: _useStateModel.handwritingKey, + ); + }), + PageNumberBox(), + PreviousNutton(), // 上一页按钮 + NextPageButton(), // 下一题按钮 + ], + ), + ), + $BottomPlaybar(handwritingLogic), + ], + ); + } +} + +class UseMainBoxState with RequestToolMixin { + final String homeworkId; + final int studentId; + final int? templateId; + int? questionNo; + final ValueNotifier pageNum; + final ValueNotifier pageCount; + final ValueNotifier handwritingData; + final ValueNotifier handwritingDetail; + + final ValueNotifier playPause; // 播放暂停 + final ValueNotifier constantFastSpeed; // 原速、快速 默认原速 + + GlobalKey<_HandwritingDrawBoxState> handwritingKey; + + UseMainBoxState._({ + required this.homeworkId, + required this.templateId, + required this.studentId, + required this.pageNum, + required this.handwritingData, + required this.questionNo, + required this.pageCount, + required this.playPause, + required this.constantFastSpeed, + required this.handwritingDetail, + required this.handwritingKey, + }); + + // 工厂构造函数 + factory UseMainBoxState.use(String homeworkId, int studentId, [int? pageNum, int? questionNo, int? templateId]) { + return UseMainBoxState._( + homeworkId: homeworkId, + templateId: templateId, + studentId: studentId, + questionNo: questionNo, + pageNum: useState(pageNum), + handwritingData: useState(null), + handwritingDetail: useState(null), + pageCount: useState(0), + playPause: useState(false), + constantFastSpeed: useState(false), + handwritingKey: GlobalKey(), + ); + } +} + +/// 上一页 +class PreviousNutton extends StatelessWidget { + PreviousNutton({super.key}); + + final HandwritingLogic handwritingLogic = Get.find(); + + @override + Widget build(BuildContext context) { + return Obx(() { + if (handwritingLogic.resultData.value?.pageNum != null && handwritingLogic.resultData.value!.pageNum > 1) { + return Positioned( + left: 3.w, + child: FloatingActionButton( + heroTag: '点击前往上一题', + tooltip: '点击前往上一题', + focusColor: Theme.of(context).primaryColor, + backgroundColor: const Color.fromRGBO(24, 32, 32, 0.1), + elevation: 6.r, + onPressed: () => easyThrottle('answer_handwriting_previous', () { + var resultData = handwritingLogic.resultData.value; + var params = handwritingLogic.params.value; + if (resultData == null || params == null) return; + params.pageNum = resultData.pageNum - 1; + handwritingLogic.params.value = OriginalManuscriptHandwritingParams.fromJson(params.toJson()); + // handwritingLogic.params.value = params; + }), + child: Icon(Icons.arrow_back_ios, color: Colors.white, size: 22.sp), + ), + ); + } + + return const SizedBox(); + }); + } +} + +/// 下一页 +class NextPageButton extends StatelessWidget { + final HandwritingLogic handwritingLogic = Get.find(); + + NextPageButton({super.key}); + + @override + Widget build(BuildContext context) { + return Obx(() { + if (handwritingLogic.resultData.value?.pageNum != null && + handwritingLogic.resultData.value!.pageNum < handwritingLogic.resultData.value!.pageCount) { + return Positioned( + right: 3.w, + child: FloatingActionButton( + heroTag: '点击前往下一题', + tooltip: '点击前往下一题', + elevation: 6.r, + backgroundColor: const Color.fromRGBO(24, 32, 32, 0.1), + onPressed: () => easyThrottle('answer_handwriting_next', () { + var resultData = handwritingLogic.resultData.value; + var params = handwritingLogic.params.value; + if (resultData == null || params == null) return; + params.pageNum = resultData.pageNum + 1; + handwritingLogic.params.value = OriginalManuscriptHandwritingParams.fromJson(params.toJson()); + }), + child: Icon(Icons.arrow_forward_ios, color: Colors.white, size: 22.sp), + ), + ); + } + + return const SizedBox(); + }); + } +} + +// 笔记还原主框 +class HandwritingDrawBox extends StatefulWidget { + final double boxWidth; + final double boxHeight; + const HandwritingDrawBox({required this.boxWidth, required this.boxHeight, super.key}); + + @override + State createState() => _HandwritingDrawBoxState(); +} + +class _HandwritingDrawBoxState extends State with EventBusMixin { + HandwritingLogic handwritingLogic = Get.find(); // 学生答题笔迹逻辑层 + + ImageStream? imageStream; // 图片监听数据 + late ImageStreamListener theImageStreamListener; // 试题图片数据 + + late ValueNotifier> _vnHandWritings; + late ValueNotifier> _vnPrimaryHandWritings; + + late StreamSubscription showManuscriptStream; + late StreamSubscription executionDataStream; + + List> _packagedHandwritingDatas = []; + List _packagedHandwritingDataAll = []; // 总数据 + List pendingData = []; // 待执行数据 + List timers = []; + int handwritingTime = 0; + int handwritingDuration = 0; + double speed = 2.0; // 播放速度 + + @override + void initState() { + super.initState(); /* */ + + theImageStreamListener = ImageStreamListener((ImageInfo info, bool _) { + // 获取图片的宽高 + WidgetsBinding.instance.addPostFrameCallback((_) { + //需要创建的小组件 + handwritingLogic.imagInfoModel.value = TestQuestionsImageInfo( + url: handwritingLogic.resultData.value!.paperPicture, + boxWidth: widget.boxWidth, + boxHeight: widget.boxHeight, + imageWidth: info.image.width.toDouble(), + imageHeight: info.image.height.toDouble(), + ); + }); + }); + + _vnHandWritings = ValueNotifier>([]); + _vnPrimaryHandWritings = ValueNotifier>(_packagedHandwritingDataAll); + + handwritingLogic.toolbar.initialization.listen((e) { + // 数据初始化完成赋值数据 + if (e) { + _packagedHandwritingDatas = handwritingLogic.packagedHandwritingDatas.value; + _packagedHandwritingDataAll = handwritingLogic.packagedHandwritingDataAll.value; + } else { + _packagedHandwritingDatas = []; + _packagedHandwritingDataAll = []; + } + + _vnPrimaryHandWritings.value = [..._packagedHandwritingDataAll]; // 总体数据 + // eventFire(model: JobHandwritingPlaybarBus); + }); + showManuscriptStream = handwritingLogic.toolbar.showManuscript.listen((e) { + // 查看原稿控制 + if (e) { + // 查看原稿 + eventFire(model: JobHandwritingPlaybarBus(false)); // 暂停 + _vnPrimaryHandWritings.value = [..._packagedHandwritingDataAll]; + } else { + // 清空原稿数据 + _vnPrimaryHandWritings.value = []; + } + }); + + executionDataStream = handwritingLogic.toolbar.executionData.listen((e) { + print('555555555555555555555555555555'); + _vnHandWritings.value = e; + }); + + eventOn(callback: (e) { + switch (e.runtimeType) { + case JobHandwritingRunTimeBus: + var _model = (e as JobHandwritingRunTimeBus); + var _runtime = _model.runTimeVal; + handwritingDuration = _model.totalVal; + handwritingTime = _runtime; + if (_runtime <= 0) { + pendingData.clear(); + } + break; + case JobHandwritingDragProgressBarBus: + var _model = (e as JobHandwritingDragProgressBarBus); + dragProgressBarInitData(_model.changeVal, _model.totalVal); + break; + case JobHandwritingPlaybarBus: + // 播放 暂停 + var _val = e as JobHandwritingPlaybarBus; + if (_val.play) { + // 播放 + toGoPlay(); + } else { + // 暂停 + toGoPause(_val.recalculate); + } + break; + case PlaybackSpeedBus: + // 播放速度 + var _model = (e as PlaybackSpeedBus); + speed = _model.speed; + dragProgressBarInitData(handwritingDuration - handwritingTime, handwritingDuration); + break; + default: + } + }); + } + + @override + void dispose() { + imageStream?.removeListener(theImageStreamListener); + showManuscriptStream.cancel(); + executionDataStream.cancel(); + eventCancel(); + super.dispose(); + } + + // 暂停播放 + Future toGoPause(bool recalculate) async { + timers.forEach((e) { + if (e.isActive) e.cancel(); + }); + timers = []; + // 总时间-剩余时间=已经执行时间 + if (recalculate && pendingData.isNotEmpty && handwritingTime > 0 && (handwritingDuration - handwritingTime > 0)) { + // 待执行的数据不等于空 每个数据都需要减去当前暂停已经执行的时间 + pendingData = pendingData.map((e) { + return GestureHandwritingRecording( + stroke: e.stroke, + data: e.data, + usageTime: e.usageTime, + intervalTime: e.intervalTime - (handwritingDuration - handwritingTime) * 1000, + ); + }).toList(); + } + } + + /// 开始播放 + Future toGoPlay() async { + try { + var executableData = _packagedHandwritingDataAll; + if (pendingData.isNotEmpty) { + // 待执行的数据没有执行完成 就继续执行待执行数据 + executableData = pendingData; + } else { + // 需要重新添加所有数据进行播放 + pendingData.addAll(_packagedHandwritingDataAll); + handwritingLogic.toolbar.executionData.value = []; // 重置已经执行的数据 + } + handwritingLogic.toolbar.showManuscript.value = false; + executableData.forEach((e) { + var ter = Timer(Duration(milliseconds: e.intervalTime ~/ speed), () => zhixinCall(e)); + timers.add(ter); + }); + } catch (e) { + print('播放报错:$e'); + } + } + + Future zhixinCall(GestureHandwritingRecording e) async { + if (mounted) { + List trajectorys = handwritingLogic.toolbar.executionData.value..add(e); + handwritingLogic.toolbar.executionData.value = List.from(trajectorys); + pendingData.remove(e); // 执行后删除容器中的当前动作 + } + } + + /// 拖动进度条后重新初始化数据 + /// @param startTime 起始时间 单位秒 + Future dragProgressBarInitData(int startTime, int totalDuration) async { + eventFire(model: JobHandwritingPlaybarBus(false, false)); + timers.forEach((e) { + if (e.isActive) e.cancel(); + }); + timers = []; + pendingData.clear(); + + if (startTime == 0) { + handwritingLogic.toolbar.executionData.value = []; // 重置已经执行的数据 + pendingData.addAll(_packagedHandwritingDataAll); + } else { + // 待执行的数据不等于空 每个数据都需要减去当前暂停已经执行的时间 + startTime = startTime * 1000; // 转为毫秒 + List executeImmediately = []; // 立即执行数据 + List waitingExecution = []; // 等待执行数据 + + for (var i = 0; i < _packagedHandwritingDataAll.length; i++) { + var item = _packagedHandwritingDataAll[i]; + + if (item.intervalTime < startTime) { + // 需要直接装配到直接打印的容器 + executeImmediately.add(item); + } else { + var intervalTime = item.intervalTime - startTime; + // 需要等待的数据 + waitingExecution.add(GestureHandwritingRecording( + stroke: item.stroke, + data: item.data, + usageTime: item.usageTime, + intervalTime: intervalTime, + )); + } + } + + pendingData = waitingExecution; + handwritingLogic.toolbar.executionData.value = executeImmediately; + } + eventFire(model: JobHandwritingPlaybarBus(true)); + } + + @override + Widget build(BuildContext context) { + return Obx(() { + print('进入了....................'); + var paperPicture = handwritingLogic.resultData.value?.paperPicture; + if (paperPicture == null) return const SizedBox(); + + print('显示原稿:${handwritingLogic.toolbar.showManuscript.value} 数据:${_vnPrimaryHandWritings.value.length}'); + return RepaintBoundary( + child: CustomPaint( + foregroundPainter: HandWritingDrawingPainter( + ctrl: handwritingLogic.toolbar.showManuscript.value ? _vnPrimaryHandWritings : _vnHandWritings, + ), + child: $TheCachedNetworkImage( + imageUrl: paperPicture, + (context, imageProvider) { + Image imageWidget = Image(image: imageProvider, fit: BoxFit.contain); + if (handwritingLogic.imagInfoModel.value == null) { + imageStream?.removeListener(theImageStreamListener); + imageStream = imageWidget.image.resolve(const ImageConfiguration()); + imageStream?.addListener(theImageStreamListener); + } + return imageWidget; + }, + ), + ), + ); + }); + } +} + +class HandWritingDrawingPainter extends CustomPainter { + final ValueNotifier> ctrl; + HandWritingDrawingPainter({required this.ctrl}) : super(repaint: ctrl); + //[定义画笔] + final Paint paintBrush = Paint() + //画笔颜色 + ..color = Colors.black + //画笔笔触类型 + ..strokeCap = StrokeCap.round + //是否启动抗锯齿 + ..isAntiAlias = true + //绘画风格,默认为填充 + // ..style = PaintingStyle.fill + //画笔的宽度 + ..style = PaintingStyle.stroke + ..strokeWidth = 0.5.r; + + @override + void paint(Canvas canvas, Size size) { + // canvas.drawPoints(PointMode.points, thePoints, paintBrush); + canvas.save(); + + var points = ctrl.value; + var _length = points.length; + for (int i = 0; i < _length; i++) { + GestureHandwritingRecording item = points[i]; + GestureHandwritingRecording? nextItem = i + 1 < _length ? points[i + 1] : null; + + Offset offsetData = item.data; + Offset? nextOffsetData = nextItem?.data; + if (nextOffsetData != null && item.stroke == nextItem?.stroke) { + canvas.drawLine(offsetData, nextOffsetData, paintBrush); + } + } + canvas.restore(); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + if (oldDelegate is HandWritingDrawingPainter) { + var repaint = ctrl.value.length != oldDelegate.ctrl.value.length || oldDelegate.ctrl.value != ctrl.value; + print('调用是否绘制:$repaint'); + return repaint; + } + return true; // 如果 oldDelegate 不是 MyCustomPainter 的实例,则总是重绘 + } +} + +// 页码 +class PageNumberBox extends StatelessWidget { + PageNumberBox({super.key}); + + final HandwritingLogic handwritingLogic = Get.find(); // 学生答题笔迹逻辑层 + + @override + Widget build(BuildContext context) { + return Positioned( + top: 6.h, + right: 4.w, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.h), + decoration: BoxDecoration( + color: const Color.fromRGBO(0, 0, 0, 0.47), + borderRadius: BorderRadius.circular(5.r), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Obx(() { + return quickText('${handwritingLogic.resultData.value?.pageNum}', color: Colors.white, size: 11.sp, align: TextAlign.end); + }), + quickText('/', color: Colors.white, size: 10.sp, align: TextAlign.end), + Obx(() { + return quickText('${handwritingLogic.resultData.value?.pageCount}', color: Colors.white, size: 8.sp, align: TextAlign.end); + }), + ], + )), + ); + } +} + +@hwidget +Widget $bottomPlaybar(BuildContext context, HandwritingLogic handwritingLogic) { + var timeConsuming = useState(0); + var handwritingInfo = useState(null); + + var usePlaybar = UseBottomPlaybar.use(timeConsuming.value); + useValueChanged(timeConsuming.value, (_, __) { + usePlaybar.playTimingSuspend(); + usePlaybar.playPause.value = false; + var seds = timeConsuming.value ~/ 1000; + if ((timeConsuming.value % 1000) > 500) seds += 1; + usePlaybar.handwritingDuration.value = seds; + usePlaybar.constantFastSpeed.value = PlaybackSpeed.DOUBLE_SPEED; + }); + + useValueChanged(usePlaybar.handwritingDuration.value, (_, __) { + usePlaybar.useTime.value = usePlaybar.handwritingDuration.value; + }); + // 播放速度 + useValueChanged(usePlaybar.constantFastSpeed.value, (_, __) { + usePlaybar.eventFire(model: PlaybackSpeedBus(usePlaybar.constantFastSpeed.value.speed)); + // 播放速度变化 + usePlaybar.playTimingSuspend(); + usePlaybar.playTimingStarts(); + }); + // 计时结束监听 + useValueChanged(usePlaybar.useTime.value, (_, __) { + var _runtime = usePlaybar.useTime.value; + if (_runtime <= 0 || usePlaybar.handwritingDuration.value == _runtime) { + Future.delayed(Duration.zero, () => (usePlaybar.playPause.value = false)); // 初始化播放按钮 + } + usePlaybar.eventFire(model: JobHandwritingRunTimeBus(_runtime, usePlaybar.handwritingDuration.value)); + }); + + useEffect(() { + // 初始化 + var initialization = handwritingLogic.toolbar.initialization.listen((e) { + usePlaybar.handWritingReady.value = e; + }); + var handwritingInfoStream = handwritingLogic.handwritingInfo.listen((e) { + // 笔迹数据 + if (e == null) return; + handwritingInfo.value = e; + timeConsuming.value = e.timeConsuming; + }); + usePlaybar.eventOn(callback: (e) { + switch (e.runtimeType) { + case JobHandwritingPlaybarBus: + // 出发播放暂停 + var _val = e as JobHandwritingPlaybarBus; + if (_val.play) { + // 开始播放 + usePlaybar.playTimingStarts(); + if (!usePlaybar.playPause.value) usePlaybar.playPause.value = true; + } else { + // 暂停播放 + usePlaybar.playTimingSuspend(); + if (usePlaybar.playPause.value) usePlaybar.playPause.value = false; + } + break; + case JobHandwritingGetReadyBus: + // 作业笔迹已经计算好坐标 可以开始播放 + Future.delayed(Duration.zero, () => (usePlaybar.handWritingReady.value = true)); + break; + default: + } + }); + + return () { + try { + initialization.cancel(); + usePlaybar.eventCancel(); + handwritingInfoStream.cancel(); + usePlaybar.timer.value?.cancel(); + } catch (e) { + print(e); + } + }; + }, []); + + return Container( + height: 62.h, + padding: EdgeInsets.symmetric(horizontal: 10.w), + alignment: Alignment.center, + color: Color.fromRGBO(0, 0, 0, 0.4), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (usePlaybar.handWritingReady.value) + InkWell( + onTap: () => easyThrottle('job_handwriting_play_pause', () { + if (usePlaybar.handwritingDuration.value == 0) return ToastUtils.showInfo('没有笔迹'); + + usePlaybar.playPause.value = !usePlaybar.playPause.value; + usePlaybar.eventFire(model: JobHandwritingPlaybarBus(usePlaybar.playPause.value)); + }), + child: Icon( + !usePlaybar.playPause.value ? Icons.play_circle_outline : Icons.pause_circle_outline, + color: Colors.white, + size: 28.r, + ), + ) + else + SpinKitPouringHourGlassRefined(size: 40.sp, color: Colors.white), + SizedBox(width: 6.w), + Expanded( + child: LayoutBuilder(builder: (context, constraints) { + final double containerWidth = constraints.maxWidth; // 展示区域总宽度 + var unitScale = containerWidth / timeConsuming.value; // 单位刻度 + var pauseIntervalsLength = handwritingInfo.value?.pauseInterval.length ?? 0; + + List pauseTickMarks = handwritingInfo.value?.pauseInterval.asMap().keys.map((e) { + bool isLast = e == pauseIntervalsLength - 1; + bool isFirst = e == 0; + var item = handwritingInfo.value!.pauseInterval[e]; + return Positioned( + top: 0, + left: unitScale * item.startTime, + child: Container( + width: unitScale * (item.apart ?? 0), + height: 10.h, + decoration: BoxDecoration( + color: Color.fromRGBO(202, 201, 201, 1), + borderRadius: isFirst + ? BorderRadius.only(topLeft: Radius.circular(8.r), bottomLeft: Radius.circular(10.r)) + : (isLast ? BorderRadius.only(topRight: Radius.circular(8.r), bottomRight: Radius.circular(10.r)) : null), + ), + ), + ); + }).toList() ?? + []; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Stack( + children: [ + Container( + height: 10.h, + width: containerWidth, + decoration: BoxDecoration( + // color: Color.fromRGBO(146, 146, 146, 1), + color: Colors.white, + borderRadius: BorderRadius.circular(50.r), + ), + ), + ...pauseTickMarks, + Container( + height: 10.h, + width: containerWidth, + // color: Theme.of(context).primaryColor, + child: SliderTheme( + data: SliderTheme.of(context).copyWith( + trackHeight: 10.h, // 轨道高度 + trackShape: RoundedRectSliderTrackShape(), // 轨道形状,可以自定义 + activeTrackColor: Theme.of(context).primaryColor, // 激活的轨道颜色 + inactiveTrackColor: Colors.transparent, // 未激活的轨道颜色 + thumbShape: RoundSliderThumbShape(enabledThumbRadius: 0, disabledThumbRadius: 0), + thumbColor: Colors.white, // 滑块颜色 + overlayShape: RoundSliderOverlayShape(overlayRadius: 0), + overlayColor: Colors.black54, // 滑块外圈颜色 + // valueIndicatorShape: PaddleSliderValueIndicatorShape(), // 标签形状,可以自定义 + ), + child: Slider( + value: (usePlaybar.handwritingDuration.value - usePlaybar.useTime.value).toDouble(), + min: 0.0, + max: usePlaybar.handwritingDuration.value.toDouble(), + inactiveColor: Colors.transparent, + onChangeEnd: (value) { + if (!usePlaybar.handWritingReady.value) return; + usePlaybar.playTimingSuspend(); // 暂停计时器得暂停 + usePlaybar.eventFire(model: JobHandwritingDragProgressBarBus(value.toInt(), usePlaybar.handwritingDuration.value)); + usePlaybar.useTime.value = usePlaybar.handwritingDuration.value - value.toInt(); + }, + onChanged: (double value) { + if (!usePlaybar.handWritingReady.value) return; + usePlaybar.useTime.value = usePlaybar.handwritingDuration.value - value.toInt(); + }, + ), + ), + ), + ], + ), + SizedBox(height: 8.h), + SizedBox( + width: containerWidth, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + quickText('累计停顿:${handwritingInfo.value?.pauseCount ?? 0}次', color: Colors.white, size: 7.sp), + quickText(convertSeconds(usePlaybar.useTime.value)?.toString() ?? '', color: Colors.white, size: 7.sp), + ], + ), + ) + ], + ); + }), + ), + SizedBox(width: 16.w), + 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(handwritingLogic), + ], + ), + ], + ), + ); +} + +// 学生原稿按钮视图 +class StudentManuscriptBtn extends StatelessWidget { + final HandwritingLogic handwritingLogic; + const StudentManuscriptBtn(this.handwritingLogic, {super.key}); + + @override + Widget build(BuildContext context) { + return Obx(() { + print('显示原噶:${handwritingLogic.toolbar.showManuscript.value}'); + return InkWell( + onTap: () => easyThrottle('job_handwriting_udent_manuscript', () { + var showManuscript = handwritingLogic.toolbar.showManuscript.value; + handwritingLogic.toolbar.showManuscript.value = !showManuscript; + }), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 3.w, vertical: 1.5.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4.r), + color: handwritingLogic.toolbar.showManuscript.value ? Theme.of(context).primaryColor : Colors.grey, + ), + child: quickText('学生原稿', color: Colors.white, size: 8.sp, align: TextAlign.center), + ), + ); + }); + } +} + +class SysjTime extends StatefulWidget { + const SysjTime({super.key}); + + @override + State createState() => _SysjTimeState(); +} + +class _SysjTimeState extends State with EventBusMixin { + int useTime = 0; + @override + void initState() { + super.initState(); + // eventOn(callback: (JobHandwritingRunTimeBus e) { + // useTime = e.runTimeVal; + // toUpState(setState, () {}, mounted); + // }); + } + + @override + void dispose() { + eventCancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return quickText(convertSeconds(useTime)?.toString() ?? '', color: Colors.white, size: 7.sp); + } +} + +class UseBottomPlaybar with EventBusMixin { + final ValueNotifier handwritingDuration; // 笔迹总时长 + final ValueNotifier playPause; // 播放暂停 + final ValueNotifier constantFastSpeed; // 播放速度 + final ValueNotifier handWritingReady; + + final ValueNotifier useTime; // 耗时 单位:(秒) + + final ValueNotifier timer; + + UseBottomPlaybar._( + {required this.handWritingReady, + required this.handwritingDuration, + required this.playPause, + required this.constantFastSpeed, + required this.useTime, + required this.timer}); + + // 工厂构造函数 + factory UseBottomPlaybar.use(int milliseconds) { + int handwritingDuration = milliseconds ~/ 1000; + if ((milliseconds % 1000) > 500) handwritingDuration += 1; + return UseBottomPlaybar._( + playPause: useState(false), + constantFastSpeed: useState(PlaybackSpeed.DOUBLE_SPEED), // 默认两倍速 + useTime: useState(handwritingDuration), + timer: useState(null), + handwritingDuration: useState(handwritingDuration), + handWritingReady: useState(false), + ); + } + + /// 开始计时 + void playTimingStarts() { + if (useTime.value > 0) { + timer.value?.cancel(); + + timer.value = Timer.periodic(Duration(milliseconds: 1000 ~/ constantFastSpeed.value.speed), (theTime) { + useTime.value -= 1; + if (useTime.value < 0) { + theTime.cancel(); + timer.value?.cancel(); + timer.value = null; + useTime.value = handwritingDuration.value; + } + }); + } + } + + /// 暂停 + void playTimingSuspend() { + timer.value?.cancel(); + timer.value = null; + } +} diff --git a/making_school_asignment_app/lib/page/home_page/children/homework_review/components/original_manuscript_handwriting/configuration_files/logic.dart b/making_school_asignment_app/lib/page/home_page/children/homework_review/components/original_manuscript_handwriting/configuration_files/logic.dart new file mode 100644 index 0000000..8f78764 --- /dev/null +++ b/making_school_asignment_app/lib/page/home_page/children/homework_review/components/original_manuscript_handwriting/configuration_files/logic.dart @@ -0,0 +1,171 @@ +// 逻辑文件 +import 'dart:ui'; + +import 'package:get/get.dart'; +import 'package:making_school_asignment_app/common/job/marking_models/do_test_questions_image_info.dart'; +import 'package:making_school_asignment_app/common/job/marking_models/original_manuscript_handwriting_params.dart'; +import 'package:making_school_asignment_app/common/mixins/request_tool_mixin.dart'; +import 'package:making_school_asignment_app/common/utils/toast_utils.dart'; +import 'package:making_school_asignment_app/page/home_page/children/homework_review/components/job_handwriting.dart'; +import 'package:making_school_asignment_app/page/home_page/children/homework_review/components/original_manuscript_handwriting/configuration_files/states.dart'; + +class HandwritingLogic extends GetxController with RequestToolMixin { + Rx resultData = Rx(null); // 笔迹数据 + Rx handwritingInfo = Rx(null); // 笔迹数据 + Rx>> packagedHandwritingDatas = Rx>>([]); + Rx> packagedHandwritingDataAll = Rx>([]); // 总数据 + Rx params = Rx(null); // 参数 + Rx imagInfoModel = Rx(null); // 图片加载后的数据 + + late ToolbarControl toolbar; + + final Function backCall; + HandwritingLogic(this.backCall); + + @override + void onInit() { + toolbar = ToolbarControl( + showManuscript: Rx(false), // 查看原卷 + initialization: Rx(false), // 初始化完成 + executionData: Rx>([]), // 执行数据 + ); + ever(params, (callback) => getData()); + ever(imagInfoModel, (callback) { + // 图像数据初始化后 计算笔迹数据 + handwritingInfo.value = getHandwritingDetail(resultData.value); + getCalculatedSize(); + }); + super.onInit(); + } + + Future getData() async { + if (params.value == null) return; + try { + ToastUtils.showLoading(); + toolbar.initialization.value = false; + imagInfoModel.value = null; + var res = await getClient().getOriginalManuscriptHandwriting(params.value!); + resultData.value = res; + } catch (e) { + ToastUtils.showError('获取笔迹数据报错,请重试'); + backCall(); + } finally { + ToastUtils.dismiss(); + } + } + + // 计算尺寸 + Future getCalculatedSize() async { + if (imagInfoModel.value == null || handwritingInfo.value == null) return; + Map> mapData = handwritingInfo.value!.strokeMap; + double scaleRatio = imagInfoModel.value!.scaleRatio; + if (mapData.isNotEmpty) { + List keys = mapData.keys.toList(); + + List> _packagedHandwritingDatas = []; + List _packagedHandwritingDataAll = []; + for (var i = 0; i < keys.length; i++) { + List newTrajectoryData = mapData[keys[i]]!.map((e) { + double theX = e.x; + double theY = e.y; + if (!e.initialization) { + // 未初始化基准 + e.toInitialization(scaleRatio); + theX = e.x; + theY = e.y; + } + return GestureHandwritingRecording( + data: Offset(theX, theY), + usageTime: e.time.toInt(), + intervalTime: e.intervalTime, + stroke: e.stroke, + ); + }).toList(); + + newTrajectoryData.sort((a, b) => a.usageTime.compareTo(b.usageTime)); + _packagedHandwritingDatas.add(newTrajectoryData); + _packagedHandwritingDataAll.addAll(newTrajectoryData); + } + packagedHandwritingDatas.value = _packagedHandwritingDatas; // 分组数据 + packagedHandwritingDataAll.value = _packagedHandwritingDataAll; // 不分组数据 + toolbar.showManuscript.value = true; // 默认原稿展示 + toolbar.initialization.value = true; // 数据初始化完成 + + // Future.delayed(Duration.zero, () => eventFire(model: JobHandwritingGetReadyBus())); // 通知外部可以播放笔迹 + } + } + + HandwritingInfo? getHandwritingDetail(JobHandwriting? theData) { + if (theData == null) return null; + // 笔画分组 + // var lattices = Map>.fromIterable( + // theData.lattices, + // key: (key) => key.stroke, + // value: (value) { + // // return theData.lattices.where((item) => item.stroke == value.stroke).toList()..sort((a, b) => a.time.compareTo(b.time)); + // return theData.lattices.where((item) => item.stroke == value.stroke).toList(); + // }, + // ); + var lattices = >{}; + var theLattices = theData.lattices; + for (var i = 0; i < theLattices.length; i++) { + Lattices item = theLattices[i]; + if (!lattices.containsKey(item.stroke)) lattices[item.stroke] = []; + lattices[item.stroke]!.add(item); // 添加笔画数据 + } + + List latticeKeys = lattices.keys.toList(); // 笔画集合 + int timeConsuming = 0; + if (latticeKeys.isNotEmpty) { + // 计算总时长 + List? firstAction = lattices[latticeKeys[0]]; + List? lastAction = lattices[latticeKeys[latticeKeys.length - 1]]; + int firstTime = 0; + int lastTime = 0; + + if (firstAction?.isNotEmpty ?? false) { + // 第一个笔画集合 + firstTime = firstAction![0].time; + } + if (lastAction?.isNotEmpty ?? false) { + // 最后一笔画集合 + lastTime = lastAction![0].time; + } + timeConsuming = lastTime - firstTime; + } + var pauseCount = 0; // 停顿次数 + List pauseInterval = []; + for (var i = 0; i < latticeKeys.length; i++) { + var currentLattices = lattices[latticeKeys[i]]!; // 当前循环笔画集合 + var prevLattices = i == 0 ? null : lattices[latticeKeys[i - 1]]!; // 下一个笔画集合 + for (var j = 0; j < currentLattices.length; j++) { + var intervalTime = 0; + var lattice = currentLattices[j]; + + if (j != 0) { + var prevItem = currentLattices[j - 1]; + var adjacentSpacingTime = lattice.time - prevItem.time; + intervalTime = adjacentSpacingTime + prevItem.intervalTime; + if (adjacentSpacingTime > 5000) { + // 大于5秒算一次停顿 + pauseCount++; + pauseInterval.add(PauseIntervalTime(startTime: prevItem.intervalTime, endTime: intervalTime)); + } + } else { + if (i != 0 && prevLattices != null) { + var prevLatticeLastItem = prevLattices[prevLattices.length - 1]; + var adjacentSpacingTime = lattice.time - prevLatticeLastItem.time; + intervalTime = adjacentSpacingTime + prevLatticeLastItem.intervalTime; + if (adjacentSpacingTime > 5000) { + // 大于5秒算一次停顿 + pauseCount++; + pauseInterval.add(PauseIntervalTime(startTime: prevLatticeLastItem.intervalTime, endTime: intervalTime)); + } + } + } + lattice.intervalTime = intervalTime; + } + } + return HandwritingInfo(pauseCount, timeConsuming, lattices, pauseInterval); + } +} diff --git a/making_school_asignment_app/lib/page/home_page/children/homework_review/components/original_manuscript_handwriting/configuration_files/states.dart b/making_school_asignment_app/lib/page/home_page/children/homework_review/components/original_manuscript_handwriting/configuration_files/states.dart new file mode 100644 index 0000000..7cb1d34 --- /dev/null +++ b/making_school_asignment_app/lib/page/home_page/children/homework_review/components/original_manuscript_handwriting/configuration_files/states.dart @@ -0,0 +1,110 @@ +// 实体文件 + +import 'dart:ui'; + +import 'package:get/get.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:making_school_asignment_app/page/home_page/children/homework_review/components/job_handwriting.dart'; + +part 'states.g.dart'; + +@JsonSerializable() +class HandwritingInfo extends Object { + int pauseCount; // 停顿次数 + List pauseInterval; + int timeConsuming; // 耗时(毫秒) + Map> strokeMap; // 笔画数据 + + HandwritingInfo(this.pauseCount, this.timeConsuming, this.strokeMap, this.pauseInterval); + + factory HandwritingInfo.fromJson(Map srcJson) => _$HandwritingInfoFromJson(srcJson); + + Map toJson() => _$HandwritingInfoToJson(this); +} + +class ToolbarControl { + Rx showManuscript; // 查看原稿 + Rx initialization; + Rx> executionData; + + ToolbarControl({ + required this.executionData, + required this.showManuscript, + required this.initialization, + }); +} + +// 播放倍速 +enum PlaybackSpeed { + ORIGINAL_SPEED(name: '原速播放', speed: 1), + ONE_POINT_FIVE_SPEED(name: '1.5x播放', speed: 1.5), + DOUBLE_SPEED(name: '2.0x播放', speed: 2), + TRIPLE_SPEED(name: '3.0x播放', speed: 3), + FOUR_SPEED(name: '4.0x播放', speed: 4), + FIVE_SPEED(name: '5.0x播放', speed: 5), + SIX_SPEED(name: '6.0x播放', speed: 6); + + const PlaybackSpeed({required this.name, required this.speed}); + final double speed; + final String name; +} + +@JsonSerializable() +class PauseIntervalTime extends Object { + int? apart; + int startTime; + int endTime; + PauseIntervalTime({required this.startTime, required this.endTime}) { + apart = endTime - startTime; + } + + factory PauseIntervalTime.fromJson(Map srcJson) => _$PauseIntervalTimeFromJson(srcJson); + + Map toJson() => _$PauseIntervalTimeToJson(this); +} + +// 播放按钮 +class JobHandwritingPlaybarBus { + bool play; + bool recalculate; + JobHandwritingPlaybarBus(this.play, [this.recalculate = true]); +} + +// 笔迹是否已经准备好(笔迹计算好坐标后通知通知栏可以开始播放) +class JobHandwritingGetReadyBus { + JobHandwritingGetReadyBus(); +} + +// 笔记运行时间 +class JobHandwritingRunTimeBus { + int runTimeVal; + int totalVal; + JobHandwritingRunTimeBus(this.runTimeVal, this.totalVal); +} + +// 拖动进度条 +class JobHandwritingDragProgressBarBus { + int changeVal; + int totalVal; + JobHandwritingDragProgressBarBus(this.changeVal, this.totalVal); +} + +// 播放速度 (原速播放/快速播放) +class PlaybackSpeedBus { + double speed; + PlaybackSpeedBus(this.speed); +} + +// 手势记录(原稿笔记还原) +class GestureHandwritingRecording { + int stroke; + int usageTime; // 用时 + Offset data; + int intervalTime; // 间隔时间 + GestureHandwritingRecording({ + required this.stroke, + required this.data, + required this.usageTime, + required this.intervalTime, + }); +} diff --git a/making_school_asignment_app/lib/page/home_page/children/homework_review/components/question_paper_view.dart b/making_school_asignment_app/lib/page/home_page/children/homework_review/components/question_paper_view.dart index 64223e8..630f67a 100644 --- a/making_school_asignment_app/lib/page/home_page/children/homework_review/components/question_paper_view.dart +++ b/making_school_asignment_app/lib/page/home_page/children/homework_review/components/question_paper_view.dart @@ -1,12 +1,9 @@ -import 'dart:async'; - import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:functional_widget_annotation/functional_widget_annotation.dart'; import 'package:get/get.dart'; -import 'package:making_school_asignment_app/common/config/request_config.dart'; import 'package:making_school_asignment_app/common/job/marking_models/do_paper_bus.dart'; import 'package:making_school_asignment_app/common/job/marking_models/do_paper_details_param.dart'; import 'package:making_school_asignment_app/common/job/marking_models/do_paper_details_result.dart'; @@ -52,7 +49,7 @@ class _QuestionPaperViewState extends State { // 主图 QuestionImageView(maxWidth, maxHeight, sateData, annotationState, logic), // 继续批阅按钮 - Positioned(right: 3.w, bottom: 4.h, child: const $ContinueToReview(isFloatingAction: true)), + // Positioned(right: 3.w, bottom: 4.h, child: const $ContinueToReview(isFloatingAction: true)), // 上一题按钮 Positioned( left: 2.w, @@ -126,6 +123,7 @@ class _QuestionPaperViewState extends State { } } +// 底部已阅数量和待阅数量 @swidget Widget $totalSubmitCountView(BuildContext context, HomeworkReviewState sateData) { return Obx(() { @@ -136,75 +134,86 @@ Widget $totalSubmitCountView(BuildContext context, HomeworkReviewState sateData) right: 8.w, child: InkWell( onTap: () async { - // List? students = await getStudents(); - // if (students == null) return; - // students = students..sort((e, e1) => e.studentName.compareTo(e1.studentName)); - // showModalBottomSheet( - // context: context, - // elevation: 10, - // backgroundColor: Colors.white, - // shape: RoundedRectangleBorder( - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(10.r), - // topRight: Radius.circular(10.r), - // ), - // ), - // builder: (BuildContext context) { - // return Padding( - // padding: EdgeInsets.symmetric(horizontal: 2.w), - // child: Column( - // children: [ - // Container( - // margin: EdgeInsets.only(top: 14.h), - // child: quickText( - // '当前页未提交学生名单', - // size: 15.sp, - // fontWeight: FontWeight.bold, - // color: Color.fromRGBO(60, 60, 60, 1), - // ), - // ), - // SizedBox(height: 10.h), - // Expanded( - // child: ListView( - // padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 4.w), - // children: [ - // Wrap( - // spacing: 6.0, // 主轴(水平)方向间距 - // runSpacing: 4.0, // 纵轴(垂直)方向间距 - // alignment: WrapAlignment.spaceAround, //沿主轴方向居中 - // children: students!.map((e) { - // return Chip( - // labelPadding: EdgeInsets.symmetric(vertical: 1.5.h, horizontal: 5.w), - // backgroundColor: Color.fromRGBO(239, 242, 255, 1), - // // avatar: CircleAvatar( - // // backgroundColor: Colors.white, - // // child: quickText(e.studentName.substring(0, 1), - // // size: 12.sp, color: Theme.of(context).primaryColor), - // // ), - // label: quickText(e.studentName, color: Color.fromRGBO(80, 94, 110, 1), size: 12.sp), - // ); - // }).toList(), - // ), - // ], - // ), - // ) - // ], - // ), - // ); - // }, - // ); + var submitStudents = sateData.data.value?.students; // 已提交学生 + + showModalBottomSheet( + context: context, + elevation: 10, + backgroundColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(10.r), topRight: Radius.circular(10.r))), + builder: (BuildContext context) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 2.w), + child: Column( + children: [ + Container( + margin: EdgeInsets.only(top: 14.h), + child: quickText( + '第${sateData.data.value!.templateIdKeyMap?[sateData.data.value!.templateId]}页未提交学生名单', + size: 15.sp, + fontWeight: FontWeight.bold, + color: const Color.fromRGBO(60, 60, 60, 1), + ), + ), + SizedBox(height: 10.h), + Expanded( + child: ListView( + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 4.w), + children: [ + Wrap( + spacing: 7.2.w, // 主轴(水平)方向间距 + runSpacing: 12.h, // 纵轴(垂直)方向间距 + alignment: WrapAlignment.start, //沿主轴方向居中 + children: submitStudents!.map((e) { + return Stack( + alignment: const FractionalOffset(0.05, 0.09), + children: [ + Container( + padding: EdgeInsets.only(top: 1.2.h, bottom: 1.5.h, left: 13.w, right: 5.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4.r), + color: const Color.fromRGBO(239, 242, 255, 1), + ), + child: quickText( + e.name, + size: 12.sp, + wordSpacing: 2, + color: const Color.fromRGBO(80, 94, 110, 1), + ), + ), + Stack( + alignment: const FractionalOffset(0.52, 0.24), + children: [ + Icon( + const IconData(0xe63d, fontFamily: "AlibabaIcon"), + size: 12.sp, + color: e.isPriority ? const Color.fromRGBO(76, 199, 147, 1) : const Color.fromRGBO(164, 164, 164, 1), + ), + quickText('优先', size: 4.sp, color: Colors.white), + ], + ), + ], + ); + }).toList(), + ), + ], + ), + ) + ], + ), + ); + }, + ); }, - child: Container( - child: Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - quickText('已阅', color: const Color.fromRGBO(117, 117, 117, 1), size: 10.sp), - quickText(data.annotatedCount, color: const Color.fromRGBO(76, 199, 147, 1), size: 12.sp, fontWeight: FontWeight.bold), - quickText('/', color: const Color.fromRGBO(117, 117, 117, 1), size: 12.sp), - quickText(data.submitCount - data.annotatedCount, color: const Color.fromRGBO(117, 117, 117, 1), size: 10.sp), - ], - ), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + quickText('已阅', color: const Color.fromRGBO(117, 117, 117, 1), size: 10.sp), + quickText(data.annotatedCount, color: const Color.fromRGBO(76, 199, 147, 1), size: 12.sp, fontWeight: FontWeight.bold), + quickText('/', color: const Color.fromRGBO(117, 117, 117, 1), size: 12.sp), + quickText(data.submitCount - data.annotatedCount, color: const Color.fromRGBO(117, 117, 117, 1), size: 10.sp), + ], ), ), ); diff --git a/making_school_asignment_app/lib/page/home_page/children/homework_review/index.dart b/making_school_asignment_app/lib/page/home_page/children/homework_review/index.dart index 453cf0d..e04bc97 100644 --- a/making_school_asignment_app/lib/page/home_page/children/homework_review/index.dart +++ b/making_school_asignment_app/lib/page/home_page/children/homework_review/index.dart @@ -62,7 +62,6 @@ class _HomeworkReviewState extends State { const DropdownSwitchStudentsType(), SizedBox(height: 1.h), const Expanded(child: QuestionPaperView()), - const BottomAnnotationSwitch() ], ), diff --git a/making_school_asignment_app/lib/page/home_page/children/student_personal/student_personal_view.dart b/making_school_asignment_app/lib/page/home_page/children/student_personal/student_personal_view.dart index 62707a3..2a159ef 100644 --- a/making_school_asignment_app/lib/page/home_page/children/student_personal/student_personal_view.dart +++ b/making_school_asignment_app/lib/page/home_page/children/student_personal/student_personal_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:making_school_asignment_app/common/utils/utils.dart'; import 'package:making_school_asignment_app/page/global_widget/ReturnToHomepage.dart'; +import 'package:making_school_asignment_app/page/home_page/children/homework_review/components/original_manuscript_handwriting/answer_handwriting_view.dart'; import 'package:making_school_asignment_app/page/home_page/children/student_personal/widget/student_kg_table.dart'; import 'package:making_school_asignment_app/page/home_page/children/student_personal/widget/student_zg_table.dart'; import 'package:making_school_asignment_app/routes/app_pages.dart'; @@ -74,11 +75,7 @@ class _StudentPersonalPageState extends State { width: 10.r, ), InkWell( - onTap: () { - /* showAnswerHandwriting(context, jobId: widget.jobId, studentId: widget.studentId).then((value) { - ref.read(jobHandwritingDrawingTrajectoryProvider.notifier).setVal([]); - });*/ - }, + onTap: () => showAnswerHandwriting(context, homeworkId: state.homeworkId, studentId: state.studentId), child: Container( width: 93.r, height: 28.r,