no message

This commit is contained in:
1147192855@qq.com 2024-06-14 17:17:24 +08:00
parent d62560184d
commit 4b92774cc8
7 changed files with 257 additions and 89 deletions

View File

@ -0,0 +1,22 @@
import 'package:json_annotation/json_annotation.dart';
part 'do_paper_bus.g.dart';
// BUG
@JsonSerializable()
class BottomOperationBar extends Object {
@JsonKey(name: 'revoke_pre_step') //
bool revokePreStep;
@JsonKey(name: 'revoke_all') //
bool revokeAll;
BottomOperationBar({
required this.revokePreStep,
required this.revokeAll,
});
factory BottomOperationBar.fromJson(Map<String, dynamic> srcJson) => _$BottomOperationBarFromJson(srcJson);
Map<String, dynamic> toJson() => _$BottomOperationBarToJson(this);
}

View File

@ -4,6 +4,9 @@ part 'do_test_questions_image_info.g.dart';
@JsonSerializable() @JsonSerializable()
class TestQuestionsImageInfo extends Object { class TestQuestionsImageInfo extends Object {
@JsonKey(name: 'templateId')
int? templateId;
@JsonKey(name: 'boxHeight') @JsonKey(name: 'boxHeight')
double boxHeight; double boxHeight;
@ -28,6 +31,12 @@ class TestQuestionsImageInfo extends Object {
@JsonKey(name: 'remainingHeight') // .() @JsonKey(name: 'remainingHeight') // .()
double remainingHeight; double remainingHeight;
@JsonKey(name: 'imageHeightOffsetStart') // Y
double imageHeightOffsetStart;
@JsonKey(name: 'imageHeightOffsetend') // Y
double imageHeightOffsetend;
// //
@JsonKey(name: 'scaleRatio') @JsonKey(name: 'scaleRatio')
double scaleRatio; double scaleRatio;
@ -42,17 +51,22 @@ class TestQuestionsImageInfo extends Object {
required this.boxHeight, required this.boxHeight,
required this.imageWidth, required this.imageWidth,
required this.imageHeight, required this.imageHeight,
this.templateId,
this.scaleRatio = 1, this.scaleRatio = 1,
this.zoom = 1, this.zoom = 1,
this.actualImgWidth = 0, this.actualImgWidth = 0,
this.actualImgHeight = 0, this.actualImgHeight = 0,
this.remainingHeight = 0, this.remainingHeight = 0,
this.imageHeightOffsetStart = 0,
this.imageHeightOffsetend = 0,
}) { }) {
// //
scaleRatio = boxWidth / imageWidth; scaleRatio = boxWidth / imageWidth;
actualImgWidth = imageWidth * scaleRatio; actualImgWidth = imageWidth * scaleRatio;
actualImgHeight = imageHeight * scaleRatio; actualImgHeight = imageHeight * scaleRatio;
remainingHeight = boxHeight - actualImgHeight; remainingHeight = boxHeight - actualImgHeight;
imageHeightOffsetStart = remainingHeight / 2;
imageHeightOffsetend = imageHeightOffsetStart + actualImgHeight;
} }
factory TestQuestionsImageInfo.fromJson(Map<String, dynamic> srcJson) => _$TestQuestionsImageInfoFromJson(srcJson); factory TestQuestionsImageInfo.fromJson(Map<String, dynamic> srcJson) => _$TestQuestionsImageInfoFromJson(srcJson);

View File

@ -0,0 +1,31 @@
// event_bus
import 'dart:async';
import 'package:event_bus/event_bus.dart';
import 'package:making_school_asignment_app/common/utils/event_bus_utils.dart';
mixin EventBusMixin<T> {
StreamSubscription? _subscription;
static final EventBusUtils _exampleUtil = EventBusUtils();
static final EventBus _eBus = _exampleUtil.getEventBus();
/*
* 线
* @param {Function} callback
*/
/// 线 @param {Function} callback
StreamSubscription eventOn<T>({required void Function(T) callback}) {
_subscription = _eBus.on<T>().listen(callback);
return _subscription!;
}
// 线
void eventFire({required T model}) {
_exampleUtil.toFire<T>(model);
}
// 线
void eventCancel() {
_subscription?.cancel();
}
}

View File

@ -0,0 +1,26 @@
import 'package:event_bus/event_bus.dart';
//
typedef EventCallback = void Function(dynamic arg);
///线
class EventBusUtils {
late final EventBus _eventBus;
//
static final EventBusUtils _instance = EventBusUtils._internal();
factory EventBusUtils() => _instance;
EventBusUtils._internal() {
_eventBus = EventBus();
}
//
EventBus getEventBus() {
return _eventBus;
}
//
void toFire<T>(T model) {
_eventBus.fire(model);
}
}

View File

@ -2,6 +2,8 @@ import 'dart:async';
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:get/get.dart'; import 'package:get/get.dart';
import 'package:making_school_asignment_app/common/job/marking_models/do_paper_bus.dart';
import 'package:making_school_asignment_app/common/mixins/event_bus_mixin.dart';
import 'package:making_school_asignment_app/common/utils/anti_shake_throttling.dart'; import 'package:making_school_asignment_app/common/utils/anti_shake_throttling.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/my_text.dart'; import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
@ -16,7 +18,7 @@ class BottomAnnotationSwitch extends StatefulWidget {
State<BottomAnnotationSwitch> createState() => _BottomAnnotationSwitchJobState(); State<BottomAnnotationSwitch> createState() => _BottomAnnotationSwitchJobState();
} }
class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch> with SingleTickerProviderStateMixin { class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch> with SingleTickerProviderStateMixin, EventBusMixin<BottomOperationBar> {
late AnimationController _animationController; // late AnimationController _animationController; //
final _homeworkLogic = Get.find<HomeworkReviewLogic>(); final _homeworkLogic = Get.find<HomeworkReviewLogic>();
final _logicControl = Get.find<HomeworkReviewLogic>().annotationState; final _logicControl = Get.find<HomeworkReviewLogic>().annotationState;
@ -39,6 +41,7 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch> with
_animationController.reverse(); _animationController.reverse();
} }
}); });
super.initState(); super.initState();
} }
@ -48,6 +51,8 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch> with
..removeListener(toUp) ..removeListener(toUp)
..dispose(); ..dispose();
_opControllisten?.cancel(); _opControllisten?.cancel();
eventCancel();
super.dispose(); super.dispose();
} }
@ -132,7 +137,7 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch> with
], ],
), ),
), ),
Container(width: double.infinity, color: Colors.white, height: 0.5.h), Container(width: double.infinity, color: Colors.white, height: 0.35.h),
Expanded( Expanded(
child: Row( child: Row(
children: [ children: [
@ -142,19 +147,26 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch> with
width: double.infinity, width: double.infinity,
height: double.infinity, height: double.infinity,
child: IconButton( child: IconButton(
onPressed: () => easyThrottle('homework_bottom_action_bar_annotations', () {}), onPressed: () => easyThrottle(
'homework_bottom_action_bar_annotations',
() => eventFire(model: BottomOperationBar(revokeAll: false, revokePreStep: true)),
),
icon: Icon(const IconData(0xe638, fontFamily: "AlibabaIcon"), size: iconSize, color: defaultColor), icon: Icon(const IconData(0xe638, fontFamily: "AlibabaIcon"), size: iconSize, color: defaultColor),
), ),
), ),
), ),
Container(width: 0.3.w, height: double.infinity, color: Colors.white), Container(width: 0.3.w, height: double.infinity, color: Colors.white),
//
Expanded( Expanded(
child: SizedBox( child: SizedBox(
width: double.infinity, width: double.infinity,
height: double.infinity, height: double.infinity,
child: IconButton( child: IconButton(
icon: Icon(const IconData(0xe637, fontFamily: "AlibabaIcon"), size: iconSize, color: defaultColor), icon: Icon(const IconData(0xe637, fontFamily: "AlibabaIcon"), size: iconSize, color: defaultColor),
onPressed: () => easyThrottle('homework_bottom_action_bar_annotations', () {}), onPressed: () => easyThrottle(
'homework_bottom_action_bar_annotations',
() => eventFire(model: BottomOperationBar(revokeAll: true, revokePreStep: false)),
),
), ),
), ),
), ),

View File

@ -6,9 +6,11 @@ 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/config/request_config.dart';
import 'package:making_school_asignment_app/common/job/marking_models/do_paper_bus.dart';
import 'package:making_school_asignment_app/common/job/marking_models/do_paper_details_param.dart'; import 'package:making_school_asignment_app/common/job/marking_models/do_paper_details_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/do_test_questions_image_info.dart'; import 'package:making_school_asignment_app/common/job/marking_models/do_test_questions_image_info.dart';
import 'package:making_school_asignment_app/common/mixins/event_bus_mixin.dart';
import 'package:making_school_asignment_app/common/utils/anti_shake_throttling.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/cached_network_img.dart';
import 'package:making_school_asignment_app/page/global_widget/my_text.dart'; import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
@ -30,6 +32,7 @@ class QuestionPaperView extends StatefulWidget {
class _QuestionPaperViewState extends State<QuestionPaperView> { class _QuestionPaperViewState extends State<QuestionPaperView> {
final logic = Get.find<HomeworkReviewLogic>(); final logic = Get.find<HomeworkReviewLogic>();
final sateData = Get.find<HomeworkReviewLogic>().state; final sateData = Get.find<HomeworkReviewLogic>().state;
final annotationState = Get.find<HomeworkReviewLogic>().annotationState;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -44,7 +47,7 @@ class _QuestionPaperViewState extends State<QuestionPaperView> {
return Stack( return Stack(
children: [ children: [
// //
$QuestionImageView(maxWidth, maxHeight, sateData), QuestionImageView(maxWidth, maxHeight, sateData, annotationState),
// //
Positioned(right: 3.w, bottom: 4.h, child: const $ContinueToReview(isFloatingAction: true)), Positioned(right: 3.w, bottom: 4.h, child: const $ContinueToReview(isFloatingAction: true)),
// //
@ -111,9 +114,10 @@ Widget $questionNumberView(BuildContext context, HomeworkReviewState sateData) {
final scrollControllerNum = useScrollController(); // final scrollControllerNum = useScrollController(); //
useEffect(() { useEffect(() {
StreamSubscription listenVal = sateData.slide.listen((e) => scrollControllerNum.jumpTo(e)); var listenVal = sateData.slide.listen((e) {
if (e != scrollControllerNum.offset) scrollControllerNum.jumpTo(e);
});
//
return () { return () {
listenVal.cancel(); listenVal.cancel();
}; };
@ -141,8 +145,12 @@ Widget $questionNumberView(BuildContext context, HomeworkReviewState sateData) {
var imageVal = sateData.imageScale.value; var imageVal = sateData.imageScale.value;
if (imageVal == null) return const SizedBox(); if (imageVal == null) return const SizedBox();
var studentQuestions = sateData.studentQuestions.value; var studentQuestions = sateData.studentQuestions.value;
print('书哈哈哈...');
return Padding( var boxHeight = imageVal.boxHeight;
var actualImgHeight = imageVal.actualImgHeight; //
return Container(
height: boxHeight > actualImgHeight ? boxHeight : actualImgHeight,
padding: EdgeInsets.only(top: imageVal.remainingHeight > 0 ? imageVal.remainingHeight / 2 : 0), padding: EdgeInsets.only(top: imageVal.remainingHeight > 0 ? imageVal.remainingHeight / 2 : 0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
@ -276,85 +284,139 @@ Widget $scoringQuestionsView(BuildContext context, StudentQuestions item, double
} }
// //
@hwidget class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar> {
Widget $questionImageView(double maxWidth, double maxHeight, HomeworkReviewState sateData) { final double maxWidth;
final scrollControllerQuestion = useScrollController(); // final double maxHeight;
var vnHandWritings = useValueNotifier<List<dynamic>>([]); final HomeworkReviewState sateData;
ImageStream? imageStream; final HomeworkReviewAnnotationsControlState annotationState;
var imageStreamListener = useState<ImageStreamListener>(ImageStreamListener((ImageInfo info, bool _) { QuestionImageView(this.maxWidth, this.maxHeight, this.sateData, this.annotationState, {super.key});
WidgetsBinding.instance.addPostFrameCallback((_) {
sateData.imageScale.value = TestQuestionsImageInfo(
boxWidth: maxWidth,
boxHeight: maxHeight,
imageWidth: info.image.width.toDouble(),
imageHeight: info.image.height.toDouble(),
url: sateData.data.value!.zgtAnswer,
);
});
}));
// ///
useEffect(() { int _findTargetIndex<T>(List<T> list, T target, [int reciprocal = 2]) {
scrollControllerQuestion.addListener(() { List<int> indexs = [];
// for (int i = list.length - 1; i >= 0; i--) {
sateData.slide.value = scrollControllerQuestion.offset; if (list[i] == target) {
}); indexs.add(i);
// if (indexs.length == reciprocal) return i;
return () { }
imageStream?.removeListener(imageStreamListener.value); }
}; return -1;
}, []); }
print('这里是加载了..............'); @override
return Container( Widget build(BuildContext context) {
height: maxHeight, final scrollControllerQuestion = useScrollController(); //
// padding: EdgeInsets.only(bottom: 2.h, top: 2.h), var vnHandWritings = useValueNotifier<List<dynamic>>([]);
alignment: Alignment.center,
child: SingleChildScrollView(
controller: scrollControllerQuestion,
physics: const BouncingScrollPhysics(),
padding: EdgeInsets.zero,
scrollDirection: Axis.vertical, //
child: Obx(() {
var imageUrl = sateData.data.value?.zgtAnswer;
if (imageUrl == null) return const SizedBox();
return Container( ImageStream? imageStream;
decoration: BoxDecoration(boxShadow: [ var imageStreamListener = useState<ImageStreamListener>(ImageStreamListener((ImageInfo info, bool _) {
BoxShadow( WidgetsBinding.instance.addPostFrameCallback((_) {
color: Colors.grey.withOpacity(0.2), sateData.imageScale.value = TestQuestionsImageInfo(
offset: Offset(-6.r, 1.r), //x轴偏移量 templateId: sateData.data.value?.templateId,
blurRadius: 10.r, // boxWidth: maxWidth,
spreadRadius: 8.r // boxHeight: maxHeight,
) imageWidth: info.image.width.toDouble(),
]), imageHeight: info.image.height.toDouble(),
child: Listener( url: sateData.data.value!.zgtAnswer,
behavior: HitTestBehavior.opaque, );
onPointerUp: (PointerUpEvent details) {}, });
onPointerMove: (PointerMoveEvent details) {}, }));
child: RepaintBoundary(
child: CustomPaint( //
foregroundPainter: DrawingPainter(ctrl: vnHandWritings), useEffect(() {
eventOn(callback: (BottomOperationBar e) async {
var annotationsData = vnHandWritings.value; //
if (e.revokeAll) {
//
if (annotationsData.isNotEmpty) {
bool? res = await showDialog<bool>(
context: context,
builder: (context1) {
return AlertDialog(content: quickText("是否撤销上次批阅批注痕迹"), actions: <Widget>[
TextButton(child: quickText("取消"), onPressed: () => Navigator.pop(context1, false)),
TextButton(child: quickText("确定", color: Theme.of(context).primaryColor), onPressed: () => Navigator.pop(context1, true))
]);
},
);
if (res == true) vnHandWritings.value = [];
}
return;
}
//
var index = _findTargetIndex(annotationsData, null);
if (index != -1) {
annotationsData = annotationsData.sublist(0, index + 1);
annotationsData = List.from(annotationsData);
} else {
annotationsData = [];
}
vnHandWritings.value = annotationsData;
});
scrollControllerQuestion.addListener(() => sateData.slide.value = scrollControllerQuestion.offset); //
//
return () {
imageStream?.removeListener(imageStreamListener.value);
};
}, []);
return Container(
height: maxHeight,
// padding: EdgeInsets.only(bottom: 2.h, top: 2.h),
alignment: Alignment.center,
child: Obx(() {
var imageUrl = sateData.data.value?.zgtAnswer;
if (imageUrl == null) return const SizedBox();
return SingleChildScrollView(
controller: scrollControllerQuestion,
physics: !annotationState.pen.value ? const BouncingScrollPhysics() : const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
scrollDirection: Axis.vertical, //
child: Container(
decoration: BoxDecoration(
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.2), offset: Offset(-6.r, 1.r), blurRadius: 10.r, spreadRadius: 8.r)]),
child: Listener(
behavior: HitTestBehavior.opaque,
onPointerUp: (PointerUpEvent details) {
//
// activePointers--;
// globalPosition = null;
var imageScale = sateData.imageScale.value;
if (imageScale == null || !annotationState.pen.value) return;
vnHandWritings.value.add(null); // 线
},
onPointerMove: (PointerMoveEvent event) {
var imageScale = sateData.imageScale.value;
if (imageScale == null || !annotationState.pen.value) return;
Offset localPosition = event.localPosition;
var dy = localPosition.dy;
if (dy > imageScale.actualImgHeight || dy < 0) return; //
vnHandWritings.value = List.from(vnHandWritings.value)..add(localPosition);
},
child: RepaintBoundary( child: RepaintBoundary(
child: $TheCachedNetworkImage( child: CustomPaint(
(context, imageProvider) { foregroundPainter: DrawingPainter(ctrl: vnHandWritings),
print('图片加载了..............'); child: RepaintBoundary(
Image imageWidget = Image(image: imageProvider, fit: BoxFit.fitWidth); child: $TheCachedNetworkImage(
imageStream?.removeListener(imageStreamListener.value); (context, imageProvider) {
imageStream = imageWidget.image.resolve(const ImageConfiguration())..addListener(imageStreamListener.value); Image imageWidget = Image(image: imageProvider, fit: BoxFit.fitWidth);
imageStream?.removeListener(imageStreamListener.value);
return imageWidget; imageStream = imageWidget.image.resolve(const ImageConfiguration())..addListener(imageStreamListener.value);
}, return imageWidget;
imageUrl: RequestConfig.imgUrl + imageUrl, },
imageUrl: RequestConfig.imgUrl + imageUrl,
),
),
), ),
), ),
), ),
), ),
), );
); }));
}), }
),
);
} }
// //
@ -363,23 +425,23 @@ class DrawingPainter extends CustomPainter {
final Paint paintBrush = Paint() final Paint paintBrush = Paint()
..color = Colors.red ..color = Colors.red
..strokeCap = StrokeCap.round ..strokeCap = StrokeCap.round
..strokeWidth = 1.5.r; ..strokeWidth = 0.6.r;
DrawingPainter({required this.ctrl}) : super(repaint: ctrl); DrawingPainter({required this.ctrl}) : super(repaint: ctrl);
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
var points = ctrl.value; var points = ctrl.value;
var pointsLength = points.length; var pointsLength = points.length;
// for (int i = 0; i < pointsLength; i++) { for (int i = 0; i < pointsLength; i++) {
// GestureRecording item = points[i]; Offset? offsetData = points[i];
// Offset? offsetData = item.data; Offset? nextOffsetData = pointsLength - 1 == i ? null : points[i + 1];
// Offset? nextOffsetData = pointsLength - 1 == i ? null : points[i + 1].data; if (offsetData != null && nextOffsetData != null) canvas.drawLine(offsetData, nextOffsetData, paintBrush);
// if (offsetData != null && nextOffsetData != null) { }
// canvas.drawLine(offsetData, nextOffsetData, paintBrush);
// }
// }
} }
@override @override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false; bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
@override
bool shouldRebuildSemantics(CustomPainter oldDelegate) => false;
} }

View File

@ -82,6 +82,7 @@ dependencies:
easy_debounce: ^2.0.3 # 防抖节流 easy_debounce: ^2.0.3 # 防抖节流
flutter_hooks: ^0.20.5 flutter_hooks: ^0.20.5
flutter_spinkit: ^5.2.1 flutter_spinkit: ^5.2.1
event_bus: ^2.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: