no message
This commit is contained in:
parent
47eb2ad17c
commit
e73d1ddaab
|
|
@ -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_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/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/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/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:retrofit/retrofit.dart';
|
||||||
import 'package:making_school_asignment_app/common/job/annotated_class.dart';
|
import 'package:making_school_asignment_app/common/job/annotated_class.dart';
|
||||||
import 'package:making_school_asignment_app/common/job/class_item.dart';
|
import 'package:making_school_asignment_app/common/job/class_item.dart';
|
||||||
|
|
@ -126,12 +128,11 @@ abstract class RetrofitClient {
|
||||||
@POST("/api/hms/Annotate/AnnotateSubmit")
|
@POST("/api/hms/Annotate/AnnotateSubmit")
|
||||||
Future reviewSubmission(@Body() ReviewSubmissionParams param);
|
Future reviewSubmission(@Body() ReviewSubmissionParams param);
|
||||||
|
|
||||||
|
// 获取原稿笔记
|
||||||
|
@GET("/api/hms/HmsReport/GetStudentPaperHandwriting")
|
||||||
|
Future<JobHandwriting> getOriginalManuscriptHandwriting(@Queries() OriginalManuscriptHandwritingParams param);
|
||||||
|
|
||||||
// OSS 上传key
|
// OSS 上传key
|
||||||
@GET("/api/infra/Oss/GetPresignedUri")
|
@GET("/api/infra/Oss/GetPresignedUri")
|
||||||
Future getOssPresignedUri(@Query('key') String key);
|
Future getOssPresignedUri(@Query('key') String key);
|
||||||
|
|
||||||
// OSS 图片上传
|
|
||||||
@PUT('{theUrl}')
|
|
||||||
@Headers(<String, dynamic>{'Content-Type': ''})
|
|
||||||
Future<void> uploadImag(@Path() String theUrl, @Part() File file);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<String, dynamic> srcJson) => _$OriginalManuscriptHandwritingParamsFromJson(srcJson);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$OriginalManuscriptHandwritingParamsToJson(this);
|
||||||
|
}
|
||||||
|
|
@ -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<String, dynamic> srcJson) => _$TimeUnitModelFromJson(srcJson);
|
||||||
|
|
||||||
|
Map<String, dynamic> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ import 'package:making_school_asignment_app/common/config/colorUtils.dart';
|
||||||
// 快捷Text使用
|
// 快捷Text使用
|
||||||
Text quickText(text,
|
Text quickText(text,
|
||||||
{double? size,
|
{double? size,
|
||||||
|
double? wordSpacing,
|
||||||
Color color = CommonColors.defaultColor,
|
Color color = CommonColors.defaultColor,
|
||||||
TextAlign? align,
|
TextAlign? align,
|
||||||
FontWeight? fontWeight,
|
FontWeight? fontWeight,
|
||||||
|
|
@ -28,6 +29,7 @@ Text quickText(text,
|
||||||
fontSize: size,
|
fontSize: size,
|
||||||
color: color,
|
color: color,
|
||||||
fontWeight: fontWeight,
|
fontWeight: fontWeight,
|
||||||
|
wordSpacing: wordSpacing,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,11 +90,42 @@ class _DropdownSwitchStudentsTypeState extends State<DropdownSwitchStudentsType>
|
||||||
value: sateData.value?.studentId,
|
value: sateData.value?.studentId,
|
||||||
underline: Container(),
|
underline: Container(),
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
|
onTap: () {
|
||||||
|
print('数据..................');
|
||||||
|
},
|
||||||
items: sateData.value?.students.map((e) {
|
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(),
|
}).toList(),
|
||||||
hint: const Text('请选择学生'), // 锚点的显示文本
|
hint: const Text('请选择学生'), // 锚点的显示文本
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
|
print('1111111111');
|
||||||
if (logic.state.param.value.studentId == value) return;
|
if (logic.state.param.value.studentId == value) return;
|
||||||
logic.state.param.value.studentId = value;
|
logic.state.param.value.studentId = value;
|
||||||
logic.state.param.value = DoPaperDetailsParam.fromJson(logic.state.param.value.toJson());
|
logic.state.param.value = DoPaperDetailsParam.fromJson(logic.state.param.value.toJson());
|
||||||
|
|
|
||||||
|
|
@ -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> 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<String, dynamic> srcJson) => _$JobHandwritingFromJson(srcJson);
|
||||||
|
|
||||||
|
Map<String, dynamic> 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<String, dynamic> srcJson) => _$LatticesFromJson(srcJson);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$LatticesToJson(this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据基准初始化坐标数据
|
||||||
|
* @param double scaleRatio
|
||||||
|
*/
|
||||||
|
void toInitialization(double scaleRatio) {
|
||||||
|
x = x * scaleRatio;
|
||||||
|
y = y * scaleRatio;
|
||||||
|
initialization = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<HandwritingLogic>().params.value = OriginalManuscriptHandwritingParams(
|
||||||
|
homeworkId: homeworkId,
|
||||||
|
studentId: studentId,
|
||||||
|
templateId: templateId,
|
||||||
|
pageNum: pageNum,
|
||||||
|
questionNo: questionNo,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final _handwritingLogic = Get.find<HandwritingLogic>();
|
||||||
|
@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<void> 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<HandwritingLogic>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主图
|
||||||
|
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<JobHandwriting?, void>(_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<int?, void>(_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<int?> pageNum;
|
||||||
|
final ValueNotifier<int> pageCount;
|
||||||
|
final ValueNotifier<JobHandwriting?> handwritingData;
|
||||||
|
final ValueNotifier<HandwritingInfo?> handwritingDetail;
|
||||||
|
|
||||||
|
final ValueNotifier<bool> playPause; // 播放暂停
|
||||||
|
final ValueNotifier<bool> 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<HandwritingLogic>();
|
||||||
|
|
||||||
|
@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<HandwritingLogic>();
|
||||||
|
|
||||||
|
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<HandwritingDrawBox> createState() => _HandwritingDrawBoxState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HandwritingDrawBoxState extends State<HandwritingDrawBox> with EventBusMixin {
|
||||||
|
HandwritingLogic handwritingLogic = Get.find<HandwritingLogic>(); // 学生答题笔迹逻辑层
|
||||||
|
|
||||||
|
ImageStream? imageStream; // 图片监听数据
|
||||||
|
late ImageStreamListener theImageStreamListener; // 试题图片数据
|
||||||
|
|
||||||
|
late ValueNotifier<List<GestureHandwritingRecording>> _vnHandWritings;
|
||||||
|
late ValueNotifier<List<GestureHandwritingRecording>> _vnPrimaryHandWritings;
|
||||||
|
|
||||||
|
late StreamSubscription showManuscriptStream;
|
||||||
|
late StreamSubscription executionDataStream;
|
||||||
|
|
||||||
|
List<List<GestureHandwritingRecording>> _packagedHandwritingDatas = [];
|
||||||
|
List<GestureHandwritingRecording> _packagedHandwritingDataAll = []; // 总数据
|
||||||
|
List<GestureHandwritingRecording> pendingData = []; // 待执行数据
|
||||||
|
List<Timer> 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<List<GestureHandwritingRecording>>([]);
|
||||||
|
_vnPrimaryHandWritings = ValueNotifier<List<GestureHandwritingRecording>>(_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<void> 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<void> 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<void> zhixinCall(GestureHandwritingRecording e) async {
|
||||||
|
if (mounted) {
|
||||||
|
List<GestureHandwritingRecording> trajectorys = handwritingLogic.toolbar.executionData.value..add(e);
|
||||||
|
handwritingLogic.toolbar.executionData.value = List.from(trajectorys);
|
||||||
|
pendingData.remove(e); // 执行后删除容器中的当前动作
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 拖动进度条后重新初始化数据
|
||||||
|
/// @param startTime 起始时间 单位秒
|
||||||
|
Future<void> 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<GestureHandwritingRecording> executeImmediately = []; // 立即执行数据
|
||||||
|
List<GestureHandwritingRecording> 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<List<GestureHandwritingRecording>> 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<HandwritingLogic>(); // 学生答题笔迹逻辑层
|
||||||
|
|
||||||
|
@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<HandwritingInfo?>(null);
|
||||||
|
|
||||||
|
var usePlaybar = UseBottomPlaybar.use(timeConsuming.value);
|
||||||
|
useValueChanged<int, void>(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<int, void>(usePlaybar.handwritingDuration.value, (_, __) {
|
||||||
|
usePlaybar.useTime.value = usePlaybar.handwritingDuration.value;
|
||||||
|
});
|
||||||
|
// 播放速度
|
||||||
|
useValueChanged<PlaybackSpeed, void>(usePlaybar.constantFastSpeed.value, (_, __) {
|
||||||
|
usePlaybar.eventFire(model: PlaybackSpeedBus(usePlaybar.constantFastSpeed.value.speed));
|
||||||
|
// 播放速度变化
|
||||||
|
usePlaybar.playTimingSuspend();
|
||||||
|
usePlaybar.playTimingStarts();
|
||||||
|
});
|
||||||
|
// 计时结束监听
|
||||||
|
useValueChanged<int, void>(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<Widget> 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<SysjTime> createState() => _SysjTimeState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SysjTimeState extends State<SysjTime> with EventBusMixin<JobHandwritingRunTimeBus> {
|
||||||
|
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<int> handwritingDuration; // 笔迹总时长
|
||||||
|
final ValueNotifier<bool> playPause; // 播放暂停
|
||||||
|
final ValueNotifier<PlaybackSpeed> constantFastSpeed; // 播放速度
|
||||||
|
final ValueNotifier<bool> handWritingReady;
|
||||||
|
|
||||||
|
final ValueNotifier<int> useTime; // 耗时 单位:(秒)
|
||||||
|
|
||||||
|
final ValueNotifier<Timer?> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<JobHandwriting?> resultData = Rx<JobHandwriting?>(null); // 笔迹数据
|
||||||
|
Rx<HandwritingInfo?> handwritingInfo = Rx<HandwritingInfo?>(null); // 笔迹数据
|
||||||
|
Rx<List<List<GestureHandwritingRecording>>> packagedHandwritingDatas = Rx<List<List<GestureHandwritingRecording>>>([]);
|
||||||
|
Rx<List<GestureHandwritingRecording>> packagedHandwritingDataAll = Rx<List<GestureHandwritingRecording>>([]); // 总数据
|
||||||
|
Rx<OriginalManuscriptHandwritingParams?> params = Rx<OriginalManuscriptHandwritingParams?>(null); // 参数
|
||||||
|
Rx<TestQuestionsImageInfo?> imagInfoModel = Rx<TestQuestionsImageInfo?>(null); // 图片加载后的数据
|
||||||
|
|
||||||
|
late ToolbarControl toolbar;
|
||||||
|
|
||||||
|
final Function backCall;
|
||||||
|
HandwritingLogic(this.backCall);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
toolbar = ToolbarControl(
|
||||||
|
showManuscript: Rx<bool>(false), // 查看原卷
|
||||||
|
initialization: Rx<bool>(false), // 初始化完成
|
||||||
|
executionData: Rx<List<GestureHandwritingRecording>>([]), // 执行数据
|
||||||
|
);
|
||||||
|
ever(params, (callback) => getData());
|
||||||
|
ever(imagInfoModel, (callback) {
|
||||||
|
// 图像数据初始化后 计算笔迹数据
|
||||||
|
handwritingInfo.value = getHandwritingDetail(resultData.value);
|
||||||
|
getCalculatedSize();
|
||||||
|
});
|
||||||
|
super.onInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> 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<void> getCalculatedSize() async {
|
||||||
|
if (imagInfoModel.value == null || handwritingInfo.value == null) return;
|
||||||
|
Map<int, List<Lattices>> mapData = handwritingInfo.value!.strokeMap;
|
||||||
|
double scaleRatio = imagInfoModel.value!.scaleRatio;
|
||||||
|
if (mapData.isNotEmpty) {
|
||||||
|
List<int> keys = mapData.keys.toList();
|
||||||
|
|
||||||
|
List<List<GestureHandwritingRecording>> _packagedHandwritingDatas = [];
|
||||||
|
List<GestureHandwritingRecording> _packagedHandwritingDataAll = [];
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
List<GestureHandwritingRecording> 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<int, List<Lattices>>.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 = <int, List<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<int> latticeKeys = lattices.keys.toList(); // 笔画集合
|
||||||
|
int timeConsuming = 0;
|
||||||
|
if (latticeKeys.isNotEmpty) {
|
||||||
|
// 计算总时长
|
||||||
|
List<Lattices>? firstAction = lattices[latticeKeys[0]];
|
||||||
|
List<Lattices>? 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<PauseIntervalTime> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<PauseIntervalTime> pauseInterval;
|
||||||
|
int timeConsuming; // 耗时(毫秒)
|
||||||
|
Map<int, List<Lattices>> strokeMap; // 笔画数据
|
||||||
|
|
||||||
|
HandwritingInfo(this.pauseCount, this.timeConsuming, this.strokeMap, this.pauseInterval);
|
||||||
|
|
||||||
|
factory HandwritingInfo.fromJson(Map<String, dynamic> srcJson) => _$HandwritingInfoFromJson(srcJson);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$HandwritingInfoToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ToolbarControl {
|
||||||
|
Rx<bool> showManuscript; // 查看原稿
|
||||||
|
Rx<bool> initialization;
|
||||||
|
Rx<List<GestureHandwritingRecording>> 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<String, dynamic> srcJson) => _$PauseIntervalTimeFromJson(srcJson);
|
||||||
|
|
||||||
|
Map<String, dynamic> 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:functional_widget_annotation/functional_widget_annotation.dart';
|
import 'package:functional_widget_annotation/functional_widget_annotation.dart';
|
||||||
import 'package:get/get.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_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_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/do_paper_details_result.dart';
|
||||||
|
|
@ -52,7 +49,7 @@ class _QuestionPaperViewState extends State<QuestionPaperView> {
|
||||||
// 主图
|
// 主图
|
||||||
QuestionImageView(maxWidth, maxHeight, sateData, annotationState, logic),
|
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(
|
Positioned(
|
||||||
left: 2.w,
|
left: 2.w,
|
||||||
|
|
@ -126,6 +123,7 @@ class _QuestionPaperViewState extends State<QuestionPaperView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 底部已阅数量和待阅数量
|
||||||
@swidget
|
@swidget
|
||||||
Widget $totalSubmitCountView(BuildContext context, HomeworkReviewState sateData) {
|
Widget $totalSubmitCountView(BuildContext context, HomeworkReviewState sateData) {
|
||||||
return Obx(() {
|
return Obx(() {
|
||||||
|
|
@ -136,75 +134,86 @@ Widget $totalSubmitCountView(BuildContext context, HomeworkReviewState sateData)
|
||||||
right: 8.w,
|
right: 8.w,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
// List<JobConcernedWithStudent>? students = await getStudents();
|
var submitStudents = sateData.data.value?.students; // 已提交学生
|
||||||
// if (students == null) return;
|
|
||||||
// students = students..sort((e, e1) => e.studentName.compareTo(e1.studentName));
|
showModalBottomSheet(
|
||||||
// showModalBottomSheet(
|
context: context,
|
||||||
// context: context,
|
elevation: 10,
|
||||||
// elevation: 10,
|
backgroundColor: Colors.white,
|
||||||
// backgroundColor: Colors.white,
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(10.r), topRight: Radius.circular(10.r))),
|
||||||
// shape: RoundedRectangleBorder(
|
builder: (BuildContext context) {
|
||||||
// borderRadius: BorderRadius.only(
|
return Padding(
|
||||||
// topLeft: Radius.circular(10.r),
|
padding: EdgeInsets.symmetric(horizontal: 2.w),
|
||||||
// topRight: Radius.circular(10.r),
|
child: Column(
|
||||||
// ),
|
children: [
|
||||||
// ),
|
Container(
|
||||||
// builder: (BuildContext context) {
|
margin: EdgeInsets.only(top: 14.h),
|
||||||
// return Padding(
|
child: quickText(
|
||||||
// padding: EdgeInsets.symmetric(horizontal: 2.w),
|
'第${sateData.data.value!.templateIdKeyMap?[sateData.data.value!.templateId]}页未提交学生名单',
|
||||||
// child: Column(
|
size: 15.sp,
|
||||||
// children: [
|
fontWeight: FontWeight.bold,
|
||||||
// Container(
|
color: const Color.fromRGBO(60, 60, 60, 1),
|
||||||
// margin: EdgeInsets.only(top: 14.h),
|
),
|
||||||
// child: quickText(
|
),
|
||||||
// '当前页未提交学生名单',
|
SizedBox(height: 10.h),
|
||||||
// size: 15.sp,
|
Expanded(
|
||||||
// fontWeight: FontWeight.bold,
|
child: ListView(
|
||||||
// color: Color.fromRGBO(60, 60, 60, 1),
|
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 4.w),
|
||||||
// ),
|
children: [
|
||||||
// ),
|
Wrap(
|
||||||
// SizedBox(height: 10.h),
|
spacing: 7.2.w, // 主轴(水平)方向间距
|
||||||
// Expanded(
|
runSpacing: 12.h, // 纵轴(垂直)方向间距
|
||||||
// child: ListView(
|
alignment: WrapAlignment.start, //沿主轴方向居中
|
||||||
// padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 4.w),
|
children: submitStudents!.map((e) {
|
||||||
// children: [
|
return Stack(
|
||||||
// Wrap(
|
alignment: const FractionalOffset(0.05, 0.09),
|
||||||
// spacing: 6.0, // 主轴(水平)方向间距
|
children: [
|
||||||
// runSpacing: 4.0, // 纵轴(垂直)方向间距
|
Container(
|
||||||
// alignment: WrapAlignment.spaceAround, //沿主轴方向居中
|
padding: EdgeInsets.only(top: 1.2.h, bottom: 1.5.h, left: 13.w, right: 5.w),
|
||||||
// children: students!.map((e) {
|
decoration: BoxDecoration(
|
||||||
// return Chip(
|
borderRadius: BorderRadius.circular(4.r),
|
||||||
// labelPadding: EdgeInsets.symmetric(vertical: 1.5.h, horizontal: 5.w),
|
color: const Color.fromRGBO(239, 242, 255, 1),
|
||||||
// backgroundColor: Color.fromRGBO(239, 242, 255, 1),
|
),
|
||||||
// // avatar: CircleAvatar(
|
child: quickText(
|
||||||
// // backgroundColor: Colors.white,
|
e.name,
|
||||||
// // child: quickText(e.studentName.substring(0, 1),
|
size: 12.sp,
|
||||||
// // size: 12.sp, color: Theme.of(context).primaryColor),
|
wordSpacing: 2,
|
||||||
// // ),
|
color: const Color.fromRGBO(80, 94, 110, 1),
|
||||||
// label: quickText(e.studentName, color: Color.fromRGBO(80, 94, 110, 1), size: 12.sp),
|
),
|
||||||
// );
|
),
|
||||||
// }).toList(),
|
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(
|
||||||
child: Row(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
children: [
|
||||||
children: [
|
quickText('已阅', color: const Color.fromRGBO(117, 117, 117, 1), size: 10.sp),
|
||||||
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(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('/', 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),
|
||||||
quickText(data.submitCount - data.annotatedCount, color: const Color.fromRGBO(117, 117, 117, 1), size: 10.sp),
|
],
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,6 @@ class _HomeworkReviewState extends State<HomeworkReview> {
|
||||||
const DropdownSwitchStudentsType(),
|
const DropdownSwitchStudentsType(),
|
||||||
SizedBox(height: 1.h),
|
SizedBox(height: 1.h),
|
||||||
const Expanded(child: QuestionPaperView()),
|
const Expanded(child: QuestionPaperView()),
|
||||||
|
|
||||||
const BottomAnnotationSwitch()
|
const BottomAnnotationSwitch()
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:making_school_asignment_app/common/utils/utils.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/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_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/page/home_page/children/student_personal/widget/student_zg_table.dart';
|
||||||
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
import 'package:making_school_asignment_app/routes/app_pages.dart';
|
||||||
|
|
@ -74,11 +75,7 @@ class _StudentPersonalPageState extends State<StudentPersonalPage> {
|
||||||
width: 10.r,
|
width: 10.r,
|
||||||
),
|
),
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () {
|
onTap: () => showAnswerHandwriting(context, homeworkId: state.homeworkId, studentId: state.studentId),
|
||||||
/* showAnswerHandwriting(context, jobId: widget.jobId, studentId: widget.studentId).then((value) {
|
|
||||||
ref.read(jobHandwritingDrawingTrajectoryProvider.notifier).setVal([]);
|
|
||||||
});*/
|
|
||||||
},
|
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 93.r,
|
width: 93.r,
|
||||||
height: 28.r,
|
height: 28.r,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue