no message

This commit is contained in:
1147192855@qq.com 2024-04-24 13:46:19 +08:00
parent aa9dd3d83c
commit 8c10e6eb4d
9 changed files with 912 additions and 154 deletions

2
.gitignore vendored
View File

@ -225,3 +225,5 @@ marking_app/lib/pages/homework_correction/job_home.g.dart
marking_app/lib/common/model/marking/keyboard_assist_event.g.dart
marking_app/lib/common/model/marking/marking_history_zoom_info.g.dart
marking_app/lib/common/model/job/job_handwriting.g.dart
marking_app/lib/utils/my_time_util.g.dart
marking_app/lib/pages/homework_correction/widget/answer_handwriting.g.dart

View File

@ -14,11 +14,30 @@ class GestureRecording {
bool scopeBox;
int intervalTime; //
GestureRecording({
required this.eraser,
required this.annotationSwitch,
this.data,
this.usageTime,
this.scopeBox = false,
this.intervalTime = 0,
});
}
/**
* (稿)
*/
class GestureHandwritingRecording {
int stroke;
int usageTime; //
Offset data;
int intervalTime; //
GestureHandwritingRecording({
required this.stroke,
required this.data,
required this.usageTime,
required this.intervalTime,
});
}

View File

@ -16,12 +16,7 @@ class JobHandwriting extends Object {
@JsonKey(name: 'pageCount')
int pageCount;
JobHandwriting(
this.lattices,
this.paperPicture,
this.pageNum,
this.pageCount,
);
JobHandwriting(this.lattices, this.paperPicture, this.pageNum, this.pageCount);
factory JobHandwriting.fromJson(Map<String, dynamic> srcJson) => _$JobHandwritingFromJson(srcJson);
@ -34,20 +29,18 @@ class Lattices extends Object {
int stroke;
@JsonKey(name: 'x')
int x;
double x;
@JsonKey(name: 'y')
int y;
double y;
@JsonKey(name: 'time')
int time;
Lattices(
this.stroke,
this.x,
this.y,
this.time,
);
@JsonKey(name: 'intervalTime')
int intervalTime;
Lattices(this.stroke, this.x, this.y, this.time, [this.intervalTime = 0]);
factory Lattices.fromJson(Map<String, dynamic> srcJson) => _$LatticesFromJson(srcJson);

View File

@ -36,12 +36,7 @@ class TestQuestionsImageInfo extends Object {
double? imageHeightOffsetend;
TestQuestionsImageInfo(
{required this.width,
required this.height,
required this.url,
required this.boxWidth,
required this.boxHeight,
this.pixelRatio = 1}) {
{required this.width, required this.height, required this.url, required this.boxWidth, required this.boxHeight, this.pixelRatio = 1}) {
// print('图片宽度:$width');
// print('图片高度:$height');
@ -60,6 +55,14 @@ class TestQuestionsImageInfo extends Object {
}
}
//
void calculateStartAndEndHeight([double otherHeight = 0]) {
if (scaleHeight != null) {
imageHeightOffsetStart = (boxHeight - (scaleHeight! + otherHeight)) / 2;
imageHeightOffsetend = imageHeightOffsetStart! + scaleHeight!;
}
}
factory TestQuestionsImageInfo.fromJson(Map<String, dynamic> srcJson) => _$TestQuestionsImageInfoFromJson(srcJson);
Map<String, dynamic> toJson() => _$TestQuestionsImageInfoToJson(this);

View File

@ -76,14 +76,17 @@ class _JobHomeState extends State<JobHome> with CommonMixin, EventBusMixin, Auto
child: ListView(
children: [
Container(
height: 200.h,
width: double.infinity,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/job_home_top_bgm.png'),
fit: BoxFit.fill, //
),
constraints: BoxConstraints(
minHeight: 200.h,
maxWidth: double.infinity,
),
// decoration: BoxDecoration(
// image: DecorationImage(
// image: AssetImage('assets/images/job_home_top_bgm.png'),
// fit: BoxFit.fitWidth, //
// ),
// ),
child: Image.asset('assets/images/job_home_top_bgm.png', fit: BoxFit.fitWidth),
),
SizedBox(height: 30.h),
SlidingData([
@ -104,8 +107,8 @@ class _JobHomeState extends State<JobHome> with CommonMixin, EventBusMixin, Auto
navigationUrl: '${RouterManager.jobStudentGroupPath}?page=set',
)
], 0),
spaceWidth,
$TermRow([EntranceModel(title: '批阅设置', image: 'assets/images/job_home_marking_set.png', navigationUrl: '')], 0),
// spaceWidth,
// $TermRow([EntranceModel(title: '批阅设置', image: 'assets/images/job_home_marking_set.png', navigationUrl: '')], 0),
],
),
),

View File

@ -0,0 +1,18 @@
/// 稿
//
import 'package:marking_app/common/model/job/gesture_recording.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:marking_app/common/mixin/common.dart';
final jobHandwritingDrawingTrajectoryProvider =
StateNotifierProvider<JobHandwritingDrawingTrajectoryProviderHandle, List<GestureHandwritingRecording>>(
(ref) => JobHandwritingDrawingTrajectoryProviderHandle([]));
class JobHandwritingDrawingTrajectoryProviderHandle extends StateNotifier<List<GestureHandwritingRecording>> with CommonMixin {
JobHandwritingDrawingTrajectoryProviderHandle(List<GestureHandwritingRecording> progress) : super(progress);
setVal(List<GestureHandwritingRecording> val) {
state = val;
}
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:marking_app/common/mixin/common.dart';
import 'package:marking_app/common/model/common/base_structure_result.dart';
import 'package:marking_app/common/model/job/job_data_report.dart';
@ -12,19 +13,20 @@ import 'package:marking_app/utils/common_utils.dart';
import 'package:marking_app/utils/request/rest_client.dart';
import 'package:marking_app/utils/toast_utils.dart';
import 'providers/handwriting_drawing_trajectory_provider.dart';
import 'widget/answer_handwriting.dart';
class QuickCheckPersonal extends StatefulWidget {
class QuickCheckPersonal extends StatefulHookConsumerWidget {
final int jobId;
final int studentId;
const QuickCheckPersonal({Key? key, required this.jobId, required this.studentId}) : super(key: key);
@override
State<QuickCheckPersonal> createState() => _QuickCheckPersonalState();
ConsumerState<QuickCheckPersonal> createState() => _QuickCheckPersonalState();
}
class _QuickCheckPersonalState extends State<QuickCheckPersonal> with CommonMixin {
class _QuickCheckPersonalState extends ConsumerState<QuickCheckPersonal> with CommonMixin {
StudentDetails? studentInfo;
void initState() {
@ -120,7 +122,9 @@ class _QuickCheckPersonalState extends State<QuickCheckPersonal> with CommonMixi
),
InkWell(
onTap: () {
showAnswerHandwriting(context, jobId: widget.jobId, studentId: widget.studentId);
showAnswerHandwriting(context, jobId: widget.jobId, studentId: widget.studentId).then((value) {
ref.read(jobHandwritingDrawingTrajectoryProvider.notifier).setVal([]);
});
},
child: Container(
width: 93.r,

View File

@ -1,12 +1,25 @@
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_screenutil/flutter_screenutil.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:functional_widget_annotation/functional_widget_annotation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:marking_app/common/mixin/common.dart';
import 'package:marking_app/common/model/job/job_handwriting.dart';
import 'package:marking_app/common/model/job/test_questions_image_info.dart';
import 'package:marking_app/pages/common/event_bus_mixin.dart';
import 'package:marking_app/pages/homework_correction/hooks/use_cached_img_refresh.dart';
import 'package:marking_app/utils/index.dart';
import 'package:marking_app/utils/my_text.dart';
import 'package:marking_app/utils/my_time_util.dart';
import '../../../common/model/job/gesture_recording.dart';
import '../providers/handwriting_drawing_trajectory_provider.dart';
part 'answer_handwriting.g.dart';
///
class AnswerHandwriting extends Dialog {
@ -21,10 +34,15 @@ class AnswerHandwriting extends Dialog {
Widget build(BuildContext context) {
return Center(
child: Container(
// color: Color.fromRGBO(0, 0, 0, 0.6),
width: ScreenUtil().screenWidth - 60.w,
height: ScreenUtil().screenHeight - 160.h,
child: AnswerHandwritingMainBox(jobId: jobId, studentId: studentId, pageNum: pageNum, questionNo: questionNo, closeCall: closeCall),
width: ScreenUtil().screenWidth - 60.r,
alignment: Alignment.center,
child: AnswerHandwritingMainBox(
jobId: jobId,
studentId: studentId,
pageNum: pageNum,
questionNo: questionNo,
closeCall: closeCall,
),
),
);
}
@ -67,6 +85,12 @@ class AnswerHandwritingMainBox extends HookWidget {
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, __) {
@ -75,132 +99,42 @@ class AnswerHandwritingMainBox extends HookWidget {
useEffect(() {
_useStateModel.getData().catchError((e) => closeCall());
_useStateModel.imageKey.value = UniqueKey();
return () {};
}, []);
JobHandwriting? _data = _useStateModel.handwritingData.value;
print('这里是build:${_useStateModel.pageNum.value}');
HandwritingInfo? _dataDetail = _useStateModel.handwritingDetail.value;
if (_data == null) return Container();
if (_data == null || _dataDetail == null) return Container();
print('数据长度:${_data.lattices.length}');
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Stack(
alignment: const FractionalOffset(0, 0.5),
children: [
Container(
child: CachedNetworkImage(
key: _useStateModel.imageKey.value,
fit: BoxFit.contain,
imageUrl: _data?.paperPicture ?? '',
imageBuilder: (context, imageProvider) {
return Image(image: imageProvider, fit: BoxFit.fitWidth);
},
placeholder: (context, url) => Center(child: SpinKitWaveSpinner(color: Theme.of(context).primaryColor, size: 50.r)),
errorWidget: (context, url, error) {
return Center(
child: GestureDetector(
onTap: () => (_useStateModel.imageKey.value = UniqueKey()),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset('assets/images/test_paper_loading_failed.png'),
quickText('加载失败,点击重试', color: Color.fromRGBO(148, 163, 182, 1), size: 12.sp),
],
),
),
);
},
),
),
if (_useStateModel.handwritingData.value != null && _useStateModel.pageNum.value != null && _useStateModel.pageNum.value! > 1)
Positioned(
left: 3.w,
top: 280.h,
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', () {
_useStateModel.pageNum.value = _useStateModel.pageNum.value! - 1;
}),
child: Icon(Icons.arrow_back_ios, color: Colors.white, size: 22.sp),
//
HandwritingDrawBox(
_data.paperPicture,
_dataDetail,
key: _useStateModel.handwritingKey,
),
$PageNumberBox(_data.pageNum),
//
$PreviousNutton(
_useStateModel.pageNum.value,
() => _useStateModel.pageNum.value = _useStateModel.pageNum.value! - 1,
),
//
if (_useStateModel.handwritingData.value != null &&
_useStateModel.pageNum.value != null &&
_useStateModel.pageNum.value! < _useStateModel.pageCount.value)
Positioned(
right: 3.w,
top: 280.h,
child: FloatingActionButton(
heroTag: '点击前往下一题',
tooltip: '点击前往下一题',
elevation: 6.r,
backgroundColor: const Color.fromRGBO(24, 32, 32, 0.1),
onPressed: () => easyThrottle('answer_handwriting_next', () {
_useStateModel.pageNum.value = _useStateModel.pageNum.value! + 1;
}),
child: Icon(Icons.arrow_forward_ios, color: Colors.white, size: 22.sp),
),
$NextPageButton(
_useStateModel.pageNum.value,
_useStateModel.pageCount.value,
() => _useStateModel.pageNum.value = _useStateModel.pageNum.value! + 1,
),
],
),
Expanded(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h),
alignment: Alignment.center,
color: Color.fromRGBO(0, 0, 0, 0.5),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
InkWell(
onTap: () {},
child: Icon(
// Icons.play_circle_outline
Icons.pause_circle_outline,
color: Colors.white,
size: 28.r,
),
),
SizedBox(width: 6.w),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 20.h,
color: Theme.of(context).primaryColor,
),
SizedBox(height: 4.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
quickText('累计停顿2次', color: Colors.white, size: 7.sp),
quickText('04:30', color: Colors.white, size: 7.sp),
],
)
],
),
),
SizedBox(width: 16.w),
InkWell(
onTap: () {},
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('原速播放', color: Color.fromRGBO(79, 114, 244, 1), size: 10.sp, align: TextAlign.center),
),
),
],
),
),
),
$BottomPlaybar(_dataDetail.timeConsuming, _dataDetail.pauseCount),
],
);
}
@ -213,7 +147,13 @@ class UseMainBoxState with CommonMixin {
final ValueNotifier<int?> pageNum;
final ValueNotifier<int> pageCount;
final ValueNotifier<JobHandwriting?> handwritingData;
final ValueNotifier<Key?> imageKey;
final ValueNotifier<HandwritingInfo?> handwritingDetail;
final ValueNotifier<bool> playPause; //
final ValueNotifier<bool> constantFastSpeed; //
GlobalKey<_HandwritingDrawBoxState> handwritingKey;
UseMainBoxState._({
required this.jobId,
required this.studentId,
@ -221,7 +161,10 @@ class UseMainBoxState with CommonMixin {
required this.handwritingData,
required this.questionNo,
required this.pageCount,
required this.imageKey,
required this.playPause,
required this.constantFastSpeed,
required this.handwritingDetail,
required this.handwritingKey,
});
//
@ -232,8 +175,11 @@ class UseMainBoxState with CommonMixin {
questionNo: questionNo,
pageNum: useState(pageNum),
handwritingData: useState(null),
handwritingDetail: useState(null),
pageCount: useState(0),
imageKey: useState(null),
playPause: useState(false),
constantFastSpeed: useState(false),
handwritingKey: GlobalKey(),
);
}
@ -244,12 +190,717 @@ class UseMainBoxState with CommonMixin {
var res = await _client.getHandwriting(jobId, studentId, questionNo, pageNum.value);
if (res?.success ?? false) {
handwritingData.value = res!.data;
if (handwritingData.value!.lattices.isEmpty) {
Future.delayed(Duration.zero, () => ToastUtils.showInfo('此页试题没有笔迹'));
}
return;
}
Future.delayed(Duration(seconds: 1), () => ToastUtils.showError(res?.message ?? '笔记数据请求失败'));
} catch (e) {
print(e);
} finally {
ToastUtils.dismiss();
}
}
HandwritingInfo? getHandwritingDetail(JobHandwriting? theData) {
if (theData == null) return null;
print('开始时间:${DateTime.now().millisecondsSinceEpoch}');
//
// 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 = Map<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); //
}
print('分组时间:${DateTime.now().millisecondsSinceEpoch}');
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;
}
print('获取数据总时间:${DateTime.now().millisecondsSinceEpoch}');
var pauseCount = 0; //
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;
if (adjacentSpacingTime > 5000) {
// 5
pauseCount++;
}
intervalTime = adjacentSpacingTime + prevItem.intervalTime;
} else {
if (i != 0 && prevLattices != null) {
var prevLatticeLastItem = prevLattices[prevLattices.length - 1];
var adjacentSpacingTime = lattice.time - prevLatticeLastItem.time;
if (adjacentSpacingTime > 5000) {
// 5
pauseCount++;
}
intervalTime = adjacentSpacingTime + prevLatticeLastItem.intervalTime;
}
}
lattice.intervalTime = intervalTime;
}
}
return HandwritingInfo(pauseCount, timeConsuming, lattices);
}
}
class HandwritingInfo {
int pauseCount; //
int timeConsuming; //
Map<int, List<Lattices>> strokeMap; //
HandwritingInfo(this.pauseCount, this.timeConsuming, this.strokeMap);
}
//
@hwidget
Widget $theCachedNetworkImage(ImageWidgetBuilder imageBuilder, {required String imageUrl}) {
UseCachedImgRefresh _useImgRefsh = UseCachedImgRefresh.use();
return CachedNetworkImage(
key: _useImgRefsh.imageKey.value,
fit: BoxFit.contain,
imageUrl: imageUrl,
imageBuilder: imageBuilder,
placeholder: (context, url) => Center(child: SpinKitWaveSpinner(color: Theme.of(context).primaryColor, size: 50.r)),
errorWidget: (context, url, error) {
return GestureDetector(
onTap: () => (_useImgRefsh.imageKey.value = UniqueKey()),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset('assets/images/test_paper_loading_failed.png'),
quickText('加载失败,点击重试', color: Color.fromRGBO(148, 163, 182, 1), size: 12.sp),
],
),
);
},
);
}
///
@swidget
Widget $previousNutton(BuildContext context, int? pageNum, Function call) {
if (pageNum != null && 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', () => call()),
child: Icon(Icons.arrow_back_ios, color: Colors.white, size: 22.sp),
),
);
return SizedBox();
}
///
@swidget
Widget $nextPageButton(BuildContext context, int? pageNum, int totalNum, Function call) {
if (pageNum != null && pageNum < totalNum)
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', () => call()),
child: Icon(Icons.arrow_forward_ios, color: Colors.white, size: 22.sp),
),
);
return SizedBox();
}
//
class HandwritingDrawBox extends StatefulHookConsumerWidget {
final String image;
final HandwritingInfo handwritingData;
// final double boxWidth;
// final double boxHeight;
const HandwritingDrawBox(this.image, this.handwritingData, {super.key});
@override
ConsumerState<HandwritingDrawBox> createState() => _HandwritingDrawBoxState();
}
class _HandwritingDrawBoxState extends ConsumerState<HandwritingDrawBox> with EventBusMixin {
ImageStream? imageStream; //
TestQuestionsImageInfo? imagInfoModel; //
late ImageStreamListener theImageStreamListener;
List<List<GestureHandwritingRecording>> _packagedHandwritingDatas = [];
List<GestureHandwritingRecording> _packagedHandwritingDataAll = [];
List<GestureHandwritingRecording> pendingData = []; //
List<Timer> timers = [];
int handwritingTime = 0;
int handwritingDuration = 0;
@override
void initState() {
super.initState();
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();
}
break;
default:
}
});
theImageStreamListener = ImageStreamListener((ImageInfo info, bool _) {
if (imagInfoModel != null) return;
imagInfoModel = TestQuestionsImageInfo(
//
boxHeight: ScreenUtil().scaleHeight, //
boxWidth: ScreenUtil().screenWidth - 60.r,
url: widget.image,
height: info.image.height.toDouble(),
width: info.image.width.toDouble(),
)..calculateStartAndEndHeight(60.h); // 60.h是底部的播放栏高度
getCalculatedSize();
});
}
@override
void dispose() {
timers.forEach((e) {
if (e.isActive) e.cancel();
});
try {
imageStream?.removeListener(theImageStreamListener);
eventCancel();
} catch (e) {}
super.dispose();
}
//
Future<void> toGoPause() async {
timers.forEach((e) {
if (e.isActive) e.cancel();
});
timers = [];
if (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();
}
}
///
/// @param startTime
Future<void> dragProgressBarInitData(int startTime, int totalDuration) async {
timers.forEach((e) {
if (e.isActive) e.cancel();
});
timers = [];
pendingData.clear();
if (startTime == 0) {
ref.read(jobHandwritingDrawingTrajectoryProvider.notifier).setVal([]);
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 {
//
waitingExecution.add(GestureHandwritingRecording(
stroke: item.stroke,
data: item.data,
usageTime: item.usageTime,
intervalTime: item.intervalTime - startTime,
));
}
}
pendingData = waitingExecution;
ref.read(jobHandwritingDrawingTrajectoryProvider.notifier).setVal(executeImmediately);
eventFire(model: JobHandwritingPlaybarBus(true));
}
}
Future<void> zhixinCall(GestureHandwritingRecording e) async {
if (mounted) {
List<GestureHandwritingRecording> trajectorys = ref.read(jobHandwritingDrawingTrajectoryProvider)..add(e);
ref.read(jobHandwritingDrawingTrajectoryProvider.notifier).setVal(List.from(trajectorys));
pendingData.remove(e); //
}
}
///
Future<void> toGoPlay() async {
handwritingTime = 0;
var executableData = _packagedHandwritingDataAll;
if (pendingData.isNotEmpty) {
//
executableData = pendingData;
} else {
pendingData.addAll(_packagedHandwritingDataAll);
ref.read(jobHandwritingDrawingTrajectoryProvider.notifier).setVal([]);
}
executableData.forEach((e) {
if (e.intervalTime == 0) {
zhixinCall(e);
} else {
var ter = Timer(Duration(milliseconds: e.intervalTime), () => zhixinCall(e));
timers.add(ter);
}
});
}
//
Future<void> getCalculatedSize() async {
if (imagInfoModel == null) return;
var dataInfo = widget.handwritingData;
Map<int, List<Lattices>> mapData = dataInfo.strokeMap;
if (mapData.isNotEmpty) {
List<int> keys = mapData.keys.toList();
for (var i = 0; i < keys.length; i++) {
List<GestureHandwritingRecording> newTrajectoryData = mapData[keys[i]]!.map((e) {
double theX = e.x * imagInfoModel!.scale!;
double theY = e.y * imagInfoModel!.scale!;
return GestureHandwritingRecording(
data: Offset(theX, theY),
usageTime: e.time.toInt(),
intervalTime: e.intervalTime,
stroke: e.stroke,
);
}).toList();
_packagedHandwritingDatas.add(newTrajectoryData); //
_packagedHandwritingDataAll.addAll(newTrajectoryData); //
}
Future.delayed(Duration.zero, () => eventFire(model: JobHandwritingGetReadyBus())); //
}
}
@override
Widget build(BuildContext context) {
List<GestureHandwritingRecording> points = ref.watch(jobHandwritingDrawingTrajectoryProvider);
return Container(
alignment: Alignment.center,
child: CustomPaint(
foregroundPainter: DrawingPainter(points: points),
// size: Size(ScreenUtil().screenWidth - 60.r, widget.boxHeight),
child: RepaintBoundary(
child: $TheCachedNetworkImage(
imageUrl: widget.image,
(context, imageProvider) {
Image imageWidget = Image(image: imageProvider, fit: BoxFit.contain);
if (imagInfoModel == null) {
imageStream?.removeListener(theImageStreamListener);
//
imageStream = imageWidget.image.resolve(ImageConfiguration());
imageStream?.addListener(theImageStreamListener);
}
return imageWidget;
},
),
),
),
);
}
}
class DrawingPainter extends CustomPainter {
List<GestureHandwritingRecording> points;
DrawingPainter({required this.points}) : super();
// Paint paintBrush = Paint()
// ..color = Colors.black
// ..strokeCap = StrokeCap.round
// ..strokeWidth = 0.5.sp;
//[]
final Paint paintBrush = Paint()
//
..color = Colors.black
//
..strokeCap = StrokeCap.round
//齿
// ..isAntiAlias = true
//
// ..style = PaintingStyle.fill
//
..strokeWidth = 0.6.r;
@override
void paint(Canvas canvas, Size size) {
// canvas.drawPoints(PointMode.points, thePoints, paintBrush);
var _length = points.length;
for (int i = 0; i < _length; i++) {
GestureHandwritingRecording item = points[i];
GestureHandwritingRecording? nextItem = i + 1 >= _length ? null : points[i + 1];
Offset? offsetData = item.data;
Offset? nextOffsetData = nextItem?.data;
if (nextOffsetData != null && item.stroke == nextItem?.stroke) {
canvas.drawLine(offsetData, nextOffsetData, paintBrush);
}
}
}
@override
bool shouldRepaint(DrawingPainter oldDelegate) => true;
}
@swidget
Widget $pageNumberBox(int pageNum) {
return Positioned(
top: 6.h,
right: 4.w,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.h),
decoration: BoxDecoration(
color: Color.fromRGBO(0, 0, 0, 0.47),
borderRadius: BorderRadius.circular(5.r),
),
child: quickText('$pageNum页', color: Colors.white, size: 10.sp),
),
);
}
@hwidget
Widget $bottomPlaybar(BuildContext context, int timeConsuming, int pauseCount) {
var usePlaybar = UseBottomPlaybar.use(timeConsuming);
useValueChanged<int, void>(timeConsuming, (_, __) {
var seds = timeConsuming ~/ 1000;
if ((timeConsuming % 1000) > 500) seds += 1;
usePlaybar.handwritingDuration.value = seds;
});
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));
});
//
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(() {
usePlaybar.eventOn(callback: (e) {
// TODO
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;
case PlaybackSpeedBus:
break;
default:
}
});
return () {
try {
usePlaybar.eventCancel();
usePlaybar.timer.value?.cancel();
} catch (e) {
print(e);
}
};
}, []);
return Container(
height: 60.h,
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h),
alignment: Alignment.center,
color: Color.fromRGBO(0, 0, 0, 0.5),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (usePlaybar.handWritingReady.value)
InkWell(
onTap: () => easyThrottle('job_handwriting_play_pause', () {
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: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 20.h,
// color: Theme.of(context).primaryColor,
child: Slider(
///
value: (usePlaybar.handwritingDuration.value - usePlaybar.useTime.value).toDouble(),
/// 0
min: 0.0,
/// 1
max: usePlaybar.handwritingDuration.value.toDouble(),
// divisions:1,
///
// activeColor: Colors.red,
///
inactiveColor: Color.fromRGBO(217, 217, 217, 1),
/// onChanged null Slider
onChangeEnd: (value) {
usePlaybar.playTimingSuspend(); //
usePlaybar.eventFire(model: JobHandwritingDragProgressBarBus(value.toInt(), usePlaybar.handwritingDuration.value));
usePlaybar.useTime.value = usePlaybar.handwritingDuration.value - value.toInt();
},
onChanged: (double value) {
usePlaybar.useTime.value = usePlaybar.handwritingDuration.value - value.toInt();
},
),
// ///
// onChangeStart: (value) => log('onChangeStart: $value'),
// ///
// onChangeEnd: (value) => log('onChangeEnd: $value'),
),
SizedBox(height: 4.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
quickText('累计停顿:$pauseCount次', color: Colors.white, size: 7.sp),
quickText(convertSeconds(usePlaybar.useTime.value)?.toString() ?? '', color: Colors.white, size: 7.sp),
],
)
],
),
),
SizedBox(width: 16.w),
InkWell(
onTap: () => easyThrottle('job_handwriting_speed', () {
var theIndex = PlaybackSpeed.values.indexOf(usePlaybar.constantFastSpeed.value);
if (theIndex == PlaybackSpeed.values.length - 1) {
theIndex = -1;
}
usePlaybar.constantFastSpeed.value = PlaybackSpeed.values[theIndex + 1];
}),
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: 10.sp,
align: TextAlign.center,
),
),
),
],
),
);
}
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.ORIGINAL_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(seconds: 1), (theTime) {
useTime.value -= 1;
if (useTime.value < 0) {
timer.value?.cancel();
timer.value = null;
useTime.value = handwritingDuration.value;
}
});
}
}
///
void playTimingSuspend() {
timer.value?.cancel();
timer.value = null;
}
}
//
class JobHandwritingPlaybarBus {
bool play;
JobHandwritingPlaybarBus(this.play);
}
// ()
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);
}
//
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);
const PlaybackSpeed({required this.name, required this.speed});
final double speed;
final String name;
}

View File

@ -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;
}
}