424 lines
16 KiB
Dart
424 lines
16 KiB
Dart
import 'dart:convert';
|
||
|
||
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/model/enum/KeyboardType.dart';
|
||
import 'package:marking_app/common/model/marking/do_marking_keyboard_model.dart';
|
||
import 'package:marking_app/common/model/marking/keyboard_assist_event.dart';
|
||
import 'package:marking_app/common/model/marking/marking_common_score_items.dart';
|
||
import 'package:marking_app/common/model/marking/marking_keyboard_sliding_position.dart';
|
||
import 'package:marking_app/common/model/marking/marking_text_question.dart';
|
||
import 'package:marking_app/pages/common/event_bus_mixin.dart';
|
||
import 'package:marking_app/provider/do_marking_provider.dart';
|
||
import 'package:marking_app/utils/index.dart';
|
||
import 'package:marking_app/utils/marking_utils/index.dart';
|
||
import 'package:marking_app/utils/my_text.dart';
|
||
|
||
// 选择型键盘
|
||
class SelectableKeyboardBottom extends StatefulHookConsumerWidget {
|
||
MarkingTextQuestion data;
|
||
int markingUserId;
|
||
String questScore; // 当前分值
|
||
double totalScore; // 总分
|
||
bool hasSubtopic = false; // 是否有小题
|
||
int subtopicIndex; // 小题下标
|
||
SynchroScoreCallback synchroScore;
|
||
GestureTapCallback submitCall;
|
||
SelectableKeyboardBottom({
|
||
required this.data,
|
||
required this.markingUserId,
|
||
required this.questScore,
|
||
required this.totalScore,
|
||
required this.synchroScore,
|
||
required this.subtopicIndex,
|
||
required this.submitCall,
|
||
Key? key,
|
||
}) : hasSubtopic = data.subQuestionDetailList.isNotEmpty,
|
||
super(key: key);
|
||
|
||
@override
|
||
_SelectableKeyboardState createState() => _SelectableKeyboardState();
|
||
}
|
||
|
||
class _SelectableKeyboardState extends ConsumerState<SelectableKeyboardBottom>
|
||
with SingleTickerProviderStateMixin, EventBusMixin<KeyboardAssistEvent> {
|
||
late AnimationController _animationController;
|
||
late ScrollController _scrollController;
|
||
|
||
bool isRight = true; // 是否是右侧打分键盘
|
||
String? _smallScore; // 小分栏打分
|
||
|
||
late List nums; // 个位数集合
|
||
late List tens = []; // 十位数集合
|
||
late RemoveListener _markingKeyboardListener;
|
||
late RemoveListener _markingSubtopicSwitchingListener;
|
||
num? _tensVal; // 当前选中十位分值
|
||
final Duration easyThrottleTime = Duration(seconds: 1);
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
// 初始化不执行
|
||
_scrollController = ScrollController()..addListener(scrollListener);
|
||
_markingSubtopicSwitchingListener = ref.read(markingSubtopicSwitchingProvider.notifier).addListener((state) {
|
||
if (widget.subtopicIndex != state) widget.subtopicIndex = state;
|
||
DoMarkingKeyboardModel preference = ref.read(markingKeyboardProvider);
|
||
initKeyboardSuper(
|
||
preference.getScoreStepSize(widget.markingUserId, widget.data.questionNum, widget.data.scoreInterval),
|
||
preference.sort!);
|
||
toUp();
|
||
}, fireImmediately: false);
|
||
_markingKeyboardListener = ref.read(markingKeyboardProvider.notifier).addListener((state) {
|
||
isRight = state.keyboard == KeyboardType.RIGHT_SELECTION;
|
||
initKeyboardSuper(
|
||
state.getScoreStepSize(widget.markingUserId, widget.data.questionNum, widget.data.scoreInterval),
|
||
state.sort!);
|
||
toUp();
|
||
});
|
||
_animationController = AnimationController(
|
||
duration: const Duration(milliseconds: 400),
|
||
lowerBound: 72,
|
||
upperBound: 144,
|
||
vsync: this,
|
||
)..addListener(toUp);
|
||
|
||
// 事件总线监听
|
||
// eventOn(callback: (KeyboardAssistEvent item) {
|
||
// bool openAuxiliary = item.openAuxiliary;
|
||
// openAuxiliary ? _animationController.forward() : _animationController.reverse();
|
||
|
||
// ref.read(markingKeyboardProvider.notifier).changeOpenAuxiliary(openAuxiliary);
|
||
// });
|
||
}
|
||
|
||
void toUp() {
|
||
toUpState(setState, () {}, mounted);
|
||
}
|
||
|
||
/** 键盘滚动位置监听 */
|
||
void scrollListener() {
|
||
// 保存当前滑动位置,这里假设使用SharedPreferences存储位置为"scroll_position" data
|
||
FastData.getInstance().setMarkingKeyboardSlidingPosition(MarkingKeyboardSlidingPosition(
|
||
id: widget.markingUserId,
|
||
questionNum: widget.data.questionNum,
|
||
position: _scrollController.position.pixels,
|
||
isVertical: true,
|
||
));
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
super.dispose();
|
||
_markingKeyboardListener();
|
||
_markingSubtopicSwitchingListener();
|
||
_animationController
|
||
..removeListener(toUp)
|
||
..dispose();
|
||
eventCancel();
|
||
}
|
||
|
||
@override
|
||
void didChangeDependencies() {
|
||
super.didChangeDependencies();
|
||
// 获取历史滑动位置,这里假设使用SharedPreferences存储位置为"scroll_position"
|
||
FastData.getInstance().getMarkingKeyboardSlidingPosition().then((historyPosition) {
|
||
print('键盘滑动位置:${historyPosition?.toJson()}');
|
||
// 检查是否有历史滑动位置并且ListView已经完成渲染
|
||
bool flag = historyPosition != null &&
|
||
historyPosition.isVertical &&
|
||
historyPosition.position >= 0 &&
|
||
_scrollController.hasClients &&
|
||
historyPosition.questionNum == widget.data.questionNum &&
|
||
historyPosition.id == widget.markingUserId;
|
||
if (flag) {
|
||
// 使用JumpTo或AnimateTo方法滚动到历史位置
|
||
_scrollController.jumpTo(historyPosition.position);
|
||
// _scrollController.animateTo(
|
||
// historyPosition.position,
|
||
// duration: Duration(milliseconds: 350),
|
||
// curve: Curves.easeInOut,
|
||
// );
|
||
}
|
||
});
|
||
}
|
||
|
||
/// 键盘计算数据
|
||
/// @params localScoreInterval 本地设置分值步长
|
||
/// @params sort 键盘排序方式
|
||
void initKeyboardSuper(double? localScoreInterval, SortKeyboard sort) {
|
||
double scoreInterval = widget.data.scoreInterval; // 服务器针对当前实体设置的分值步长
|
||
if (localScoreInterval != null && localScoreInterval != 0 && localScoreInterval != scoreInterval) {
|
||
scoreInterval = localScoreInterval;
|
||
}
|
||
print(widget.data.subQuestionDetailList.map((e) => e.toJson()));
|
||
print(widget.subtopicIndex);
|
||
double totalScore = widget.hasSubtopic
|
||
? widget.data.subQuestionDetailList[widget.subtopicIndex].subQuestionScore
|
||
: widget.totalScore;
|
||
initKeyboard(totalScore, scoreInterval, sort);
|
||
}
|
||
|
||
/// 初始化键盘
|
||
/// @params {double} totalScore 总分
|
||
/// @params {double} scoreInterval 小分步长
|
||
void initKeyboard(double totalScore, double scoreInterval, SortKeyboard sort) {
|
||
// tens.clear();
|
||
// int floorScore = totalScore.floor(); // 总分向下取整
|
||
// int singleDigit = floorScore; // 个位小数
|
||
// int numsLength = singleDigit;
|
||
// if (floorScore > 10) {
|
||
// singleDigit = floorScore;
|
||
// for (var i = 10; i < floorScore; i += 10) {
|
||
// tens.add(i);
|
||
// }
|
||
|
||
// numsLength = singleDigit;
|
||
// if (scoreInterval == 0.5) {
|
||
// numsLength = singleDigit * 2;
|
||
// }
|
||
// } else {
|
||
// if (scoreInterval == 0.5) {
|
||
// numsLength = (singleDigit) * 2;
|
||
// }
|
||
// }
|
||
// nums = List.generate(numsLength, (e) => e * scoreInterval); // 小分键盘
|
||
// if (sort != SortKeyboard.FULL_AND_AERO_TOP) {
|
||
// nums = nums.reversed.toList();
|
||
// }
|
||
tens.clear();
|
||
int floorScore = totalScore.floor(); // 总分向下取整
|
||
int singleDigit = floorScore; // 个位小数
|
||
int numsLength = singleDigit;
|
||
if (floorScore > 10) {
|
||
singleDigit = 9;
|
||
for (var i = 10; i < floorScore; i += 10) {
|
||
tens.add(i);
|
||
}
|
||
}
|
||
numsLength = ((numsLength - (numsLength % scoreInterval)) / scoreInterval).floor();
|
||
|
||
/// +1 添加满分
|
||
nums = List.generate(numsLength + 1, (e) => e * scoreInterval); // 小分键盘
|
||
// 加一个总分
|
||
if (numsLength * scoreInterval < totalScore) nums.add(totalScore);
|
||
if (sort != SortKeyboard.FULL_AND_AERO_TOP) {
|
||
nums = nums.reversed.toList();
|
||
}
|
||
}
|
||
|
||
/// 打分
|
||
/// @params {num} fraction 分数
|
||
/// @params {bool} isTens 十位分数
|
||
/// @params {bool} fullScore 满分
|
||
Future<void> marking({required num fraction, String? strScore, bool isTens = false, bool fullScore = false}) async {
|
||
// 零分的处理
|
||
bool allWrong = fraction == 0 && fullScore;
|
||
if (allWrong) {
|
||
fullScore = false;
|
||
}
|
||
// 十位
|
||
if (!fullScore && isTens) {
|
||
_tensVal = fraction;
|
||
|
||
return await widget.synchroScore(
|
||
score: fraction.toDouble(),
|
||
continueScoring: true,
|
||
hasSubtopic: widget.hasSubtopic,
|
||
);
|
||
}
|
||
|
||
EasyLoading.show(status: 'loading...');
|
||
try {
|
||
double theScore = fraction.toDouble();
|
||
if (fullScore) {
|
||
// 满分
|
||
// theScore = widget.hasSubtopic
|
||
// ? widget.data.subQuestionDetailList[widget.subtopicIndex].subQuestionScore
|
||
// : fraction.toDouble();
|
||
theScore = widget.data.totalScore;
|
||
} else if (_tensVal != null) {
|
||
// 有十位数
|
||
theScore = (_tensVal! + fraction).toDouble();
|
||
}
|
||
|
||
if (theScore > widget.totalScore) {
|
||
return ToastUtils.showInfo('评分已大于总分,请重新评分');
|
||
}
|
||
|
||
await widget
|
||
.synchroScore(
|
||
score: theScore,
|
||
continueScoring: false,
|
||
allWrong: allWrong,
|
||
hasSubtopic: widget.hasSubtopic,
|
||
)
|
||
.then((_) {
|
||
if (widget.hasSubtopic) {
|
||
// 是否有小题
|
||
List<SubQuestions> subQuestionDetailList = widget.data.subQuestionDetailList;
|
||
if (subQuestionDetailList.map((e) => e.completeRating).contains(false)) {
|
||
return;
|
||
}
|
||
}
|
||
widget.submitCall(); // 提交
|
||
});
|
||
} catch (e) {
|
||
toPrint(val: e.toString());
|
||
} finally {
|
||
EasyLoading.dismiss();
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
DoMarkingKeyboardModel model = ref.watch(markingKeyboardProvider);
|
||
SortKeyboard sort = model.sort!;
|
||
bool isBroadwise = model.screenDirection == ScreenDirection.HORIZONTAL_SCREEN; // 是否横向
|
||
MarkingCommonScoreItems? commonScores = model.commonScores;
|
||
List<double> theCommonScores = [];
|
||
if (commonScores != null &&
|
||
commonScores.questionNum == widget.data.questionNum &&
|
||
commonScores.id == widget.markingUserId) {
|
||
theCommonScores = commonScores.score;
|
||
}
|
||
|
||
double totalScore = widget.hasSubtopic
|
||
? widget.data.subQuestionDetailList[widget.subtopicIndex].subQuestionScore
|
||
: widget.totalScore;
|
||
|
||
return Container(
|
||
height: 62.h,
|
||
width: double.infinity,
|
||
padding: EdgeInsets.only(left: 4.h, right: 4.h),
|
||
decoration: BoxDecoration(
|
||
// boxShadow: [
|
||
// BoxShadow(
|
||
// color: const Color.fromRGBO(46, 91, 255, 0.2),
|
||
// offset: Offset(26.h, 0), //阴影y轴偏移量
|
||
// blurRadius: 1, //阴影模糊程度
|
||
// spreadRadius: 0.4, //阴影扩散程度
|
||
// )
|
||
// ],
|
||
// color: Colors.white,
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Expanded(
|
||
child: Container(
|
||
padding: EdgeInsets.symmetric(horizontal: 4.h, vertical: 4.h),
|
||
color: Colors.transparent,
|
||
child: ListView(
|
||
controller: _scrollController,
|
||
scrollDirection: Axis.horizontal,
|
||
children: [
|
||
InkWell(
|
||
onTap: () {
|
||
easyThrottle(
|
||
'toMarkingVal',
|
||
() => marking(
|
||
fraction: widget.totalScore, fullScore: true, strScore: widget.totalScore.toString()),
|
||
duration: easyThrottleTime);
|
||
},
|
||
child: Container(
|
||
width: isBroadwise ? 28.w : 52.w,
|
||
height: double.infinity,
|
||
alignment: Alignment.center,
|
||
decoration: BoxDecoration(
|
||
border: Border.all(
|
||
width: 1.h,
|
||
color: const Color.fromRGBO(224, 230, 255, 1),
|
||
),
|
||
color: const Color.fromRGBO(249, 250, 254, 1),
|
||
borderRadius: BorderRadius.all(Radius.circular(2.w)),
|
||
),
|
||
child: quickText(widget.hasSubtopic ? '全对' : '满分',
|
||
size: 15.sp, color: const Color.fromRGBO(148, 163, 182, 1)),
|
||
),
|
||
),
|
||
InkWell(
|
||
onTap: () {
|
||
easyThrottle('toMarkingVal', () => marking(fraction: 0, fullScore: true, strScore: '0'),
|
||
duration: easyThrottleTime);
|
||
},
|
||
child: Container(
|
||
width: isBroadwise ? 28.w : 52.w,
|
||
height: double.infinity,
|
||
alignment: Alignment.center,
|
||
margin: EdgeInsets.only(left: 3.h),
|
||
decoration: BoxDecoration(
|
||
border: Border.all(width: 1.h, color: const Color.fromRGBO(224, 230, 255, 1)),
|
||
color: const Color.fromRGBO(249, 250, 254, 1),
|
||
borderRadius: BorderRadius.all(Radius.circular(2.w)),
|
||
),
|
||
child: quickText(widget.hasSubtopic ? '全错' : '零分',
|
||
size: 15.sp, color: const Color.fromRGBO(148, 163, 182, 1)),
|
||
),
|
||
),
|
||
...[...theCommonScores, ...nums].map((e) {
|
||
String strScore = getDoubleRemoveZero(e);
|
||
bool isCurrentSelect = _smallScore == strScore; // 当前选中
|
||
|
||
bool effective = e <= totalScore;
|
||
return InkWell(
|
||
onTap: effective
|
||
? () {
|
||
easyThrottle('toMarkingVal', () => marking(fraction: e, strScore: strScore),
|
||
duration: easyThrottleTime);
|
||
}
|
||
: null,
|
||
child: Container(
|
||
width: isBroadwise ? 28.w : 52.w,
|
||
height: double.infinity,
|
||
margin: EdgeInsets.only(left: 3.h),
|
||
alignment: Alignment.center,
|
||
decoration: BoxDecoration(
|
||
border: Border.all(width: 1.h, color: const Color.fromRGBO(224, 230, 255, 1)),
|
||
color: isCurrentSelect
|
||
? Theme.of(context).primaryColor
|
||
: effective
|
||
? const Color.fromRGBO(249, 250, 254, 1)
|
||
: Colors.grey[300],
|
||
borderRadius: BorderRadius.all(Radius.circular(2.w)),
|
||
),
|
||
child: quickText(
|
||
strScore,
|
||
size: 15.sp,
|
||
color: isCurrentSelect ? Colors.white : const Color.fromRGBO(148, 163, 182, 1),
|
||
),
|
||
),
|
||
);
|
||
}).toList(),
|
||
// if (sort != SortKeyboard.FULL_AND_AERO_TOP)
|
||
// InkWell(
|
||
// onTap: () {
|
||
// easyThrottle(
|
||
// 'toMarkingVal',
|
||
// () => marking(
|
||
// fraction: widget.totalScore, fullScore: true, strScore: widget.totalScore.toString()),
|
||
// duration: easyThrottleTime);
|
||
// },
|
||
// child: Container(
|
||
// width: isBroadwise ? 28.w : 52.w,
|
||
// height: double.infinity,
|
||
// margin: EdgeInsets.only(left: 3.h),
|
||
// alignment: Alignment.center,
|
||
// decoration: BoxDecoration(
|
||
// border: Border.all(width: 1.h, color: const Color.fromRGBO(224, 230, 255, 1)),
|
||
// color: const Color.fromRGBO(249, 250, 254, 1),
|
||
// borderRadius: BorderRadius.all(Radius.circular(2.w)),
|
||
// ),
|
||
// child: quickText('满分', size: 15.sp, color: const Color.fromRGBO(148, 163, 182, 1)),
|
||
// ),
|
||
// ),
|
||
],
|
||
),
|
||
),
|
||
)
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|