Marking.Client.Moblie/marking_app/lib/pages/marking/review.dart

671 lines
22 KiB
Dart

/*
* @Author: wangyang 1147192855@qq.com
* @Date: 2022-07-15 09:20:43
* @LastEditors: wangyang 1147192855@qq.com
* @LastEditTime: 2022-07-29 10:17:22
* @FilePath: \marking_app\lib\pages\marking\review.dart
* @Description: 回评页面
*/
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:marking_app/common/model/review/additional_conditions_for_review.dart';
import 'package:marking_app/utils/drawer_util/auto_drawer.dart';
import 'package:marking_app/utils/easy_refresh/MyEmptyWidget.dart';
import 'package:marking_app/utils/my_text.dart';
import 'package:marking_app/utils/request/rest_client.dart';
import 'package:marking_app/common/mixin/common.dart';
import 'package:marking_app/common/model/common/base_page_data.dart';
import 'package:marking_app/common/model/common/base_structure_result.dart';
import 'package:marking_app/common/model/review/review_item.dart';
import 'package:marking_app/common/model/review/review_page_params.dart';
import 'package:marking_app/common/model/review/review_tab.dart';
import 'package:marking_app/components/review_item_view.dart';
import 'package:marking_app/routes/RouterManager.dart';
import 'package:marking_app/utils/easy_refresh/mixin/refresh_data_handle.dart';
import 'package:marking_app/utils/index.dart';
class Review extends StatefulWidget {
final int examSubjectId;
final int markingUserId;
final String examName;
const Review(this.markingUserId, this.examSubjectId, this.examName, {Key? key}) : super(key: key);
@override
State<Review> createState() => _ReviewState();
}
class _ReviewState extends State<Review> with CommonMixin, SingleTickerProviderStateMixin {
final GlobalKey<_TheBodyBoxState> _key = GlobalKey<_TheBodyBoxState>();
late final AdditionalConditionsForReview otherConditions = AdditionalConditionsForReview(); // 赛选条件
late final int markingUserId;
late final String examName;
late Future<List<ReviewTab>> _future;
late final TabController? _tabController;
/* 请求获取回评tab */
Future<List<ReviewTab>> getTabs() async {
RestClient client = await getClient();
BaseStructureResult<List<ReviewTab>> result = await client.getReviewTab(markingUserId.toString(), markingUserId);
if (result.code == 200 && result.data != null) {
int allNUmber = result.data!.isEmpty
? 0
: result.data!.map((e) => e.questionCount).reduce((value, element) => value + element);
List<ReviewTab> data = [ReviewTab(allNUmber, '全部'), ...result.data!];
return data;
}
return [];
}
@override
void initState() {
markingUserId = widget.markingUserId;
examName = widget.examName;
_future = getTabs()..then((value) => _tabController = TabController(length: value.length, vsync: this));
super.initState();
}
@override
void dispose() {
super.dispose();
_tabController?.dispose();
}
// 抽屉关闭回调
void autoDrawerSwitch(bool val) {}
// 筛选条件回调
Future<void> conditionsDrawerCall({required bool reset, CollationItem? item, String? testId}) async {
if (reset || (item == null && testId == null)) {
// 筛选条件重置
otherConditions.cleanCollation().whenComplete(() => _key.currentState?.setingOtherParams());
return;
}
// 同步数据
otherConditions.synchronizationInstance(item, testId);
_key.currentState?.setingOtherParams(otherParam: otherConditions);
}
@override
Widget build(BuildContext context) {
return AnnotatedRegion(
value: const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
systemNavigationBarIconBrightness: Brightness.light,
statusBarIconBrightness: Brightness.light,
statusBarBrightness: Brightness.dark,
),
child: Container(
width: ScreenUtil().scaleWidth,
height: ScreenUtil().scaleHeight,
color: Colors.white,
child: Stack(
children: [
const ReviewHeadBox(),
Scaffold(
backgroundColor: Colors.transparent,
appBar: PreferredSize(
preferredSize: Size.fromHeight(40.h),
child: AppBar(
title: quickText(
'回评',
size: 18.sp,
color: Colors.white,
),
elevation: 0,
actions: [
Container(
height: double.infinity,
padding: EdgeInsets.only(right: 6.w),
alignment: Alignment.center,
child: InkWell(
onTap: () {
_key.currentState!.seeAbnormal();
},
child: Text(
'查看异常',
style: TextStyle(fontSize: 14.sp, color: Colors.white),
),
),
)
],
backgroundColor: Colors.transparent,
),
),
body: MyFutureBuilder.buildFutureBuilder<ReviewTab>(
context,
_future,
(List<ReviewTab> data) {
return TheBodyBox(
data,
_tabController!,
markingUserId,
widget.examSubjectId,
key: _key,
);
},
),
floatingActionButton: Builder(builder: (context) {
return FloatingActionButton(
tooltip: '筛选条件',
backgroundColor: Theme.of(context).primaryColor,
foregroundColor: Colors.white,
onPressed: () {
Scaffold.of(context).openEndDrawer(); //打开右边抽屉
},
child: Icon(Icons.filter_alt_outlined, size: 24.sp),
);
}),
drawerEdgeDragWidth: 0.0,
// 右侧抽屉
endDrawer: AutoDrawer(
callback: autoDrawerSwitch,
widthPercent: 0.72,
child: ConditionsDrawer(
condition: otherConditions,
callback: conditionsDrawerCall,
),
),
),
],
),
),
);
}
}
class TheBodyBox extends StatefulWidget {
final List<ReviewTab> data;
final TabController tabController;
final int examSubjectId;
final int markingUserId;
final Function? call;
const TheBodyBox(this.data, this.tabController, this.markingUserId, this.examSubjectId, {this.call, Key? key})
: super(key: key);
@override
State<TheBodyBox> createState() => _TheBodyBoxState();
}
class _TheBodyBoxState extends State<TheBodyBox> with CommonMixin, RefreshDataHandle<ReviewItem, ReviewPageParams> {
late final int markingUserId;
late final TabController tabController;
int _tabIndex = 0;
late final List<ReviewTab> reviewTabs;
late final List<Tab> tabs = [];
late final List<EasyRefreshController> refreshCotlrs = [];
late final List<ReviewPageParams> refreshParams = [];
late final List<List<ReviewItem>> refreshDatas = [];
late final List<bool> tabRefresh = [];
@override
void initState() {
reviewTabs = widget.data;
tabController = widget.tabController;
markingUserId = widget.markingUserId;
disRefreshCotlrs();
for (ReviewTab e in reviewTabs) {
String questionNum = e.questionNum;
tabs.add(Tab(text: '$questionNum(${e.questionCount})'));
refreshCotlrs.add(EasyRefreshController());
refreshParams.add(ReviewPageParams(
qNum: questionNum == '全部' ? null : questionNum,
isException: false,
page: 1,
limit: 10,
));
refreshDatas.add([]);
tabRefresh.add(false);
}
if (tabRefresh.isNotEmpty) {
tabRefresh[0] = true;
}
super.initState();
}
/* 发起请求 */
Future<List<ReviewItem>?> toGetPageData(EasyRefreshController controller, ReviewPageParams params,
{bool isReFresh = false}) async {
RestClient client = await getClient();
// ignore: use_build_context_synchronously
BasePageData<ReviewItem>? results = await toRefreshDataWithPath(
controller,
api: client.getReviewPage,
params: params,
isReFresh: isReFresh,
pahtVal: markingUserId.toString(),
context: context,
);
if (results != null) return results.items;
}
// 查看异常
void seeAbnormal() {
ReviewTab tab = reviewTabs[_tabIndex];
String thePath =
'${RouterManager.markingReviewAbnormalPath}?markingId=$markingUserId&examSubjectId=${widget.examSubjectId}&name=${Uri.encodeComponent(tab.questionNum)}&questionNum=${Uri.encodeComponent(tab.questionNum == '全部' ? '' : tab.questionNum)}';
RouterManager.router.navigateTo(
context,
thePath,
transition: getTransition(),
);
}
// 设置其他赛选参数
void setingOtherParams({AdditionalConditionsForReview? otherParam}) {
// 同步参数、初始化上拉下拉插件
if (otherParam == null) {
for (var param in refreshParams) {
param.markingUserDetailId = null;
param.markingTimeOrderType = null;
param.markingUserDetailId = null;
}
refreshCotlrs[_tabIndex].callRefresh();
return;
}
int? markingTimeOrderType = otherParam.markingTimeOrderType;
int? scoreOrderType = otherParam.scoreOrderType;
String? questionIdStr = otherParam.questionId;
int? questionId = questionIdStr == null ? null : (questionIdStr == '' ? null : int.parse(questionIdStr));
refreshParams.asMap().keys.forEach((index) {
ReviewPageParams param = refreshParams[index];
param.markingUserDetailId = questionId;
if (markingTimeOrderType != null) {
param.markingTimeOrderType = markingTimeOrderType;
param.scoreOrderType = null;
}
if (scoreOrderType != null) {
param.scoreOrderType = scoreOrderType;
param.markingTimeOrderType = null;
}
tabRefresh[index] = false;
});
refreshCotlrs[_tabIndex].callRefresh();
}
@override
void dispose() {
super.dispose();
disRefreshCotlrs();
}
// 清空上拉加载下拉刷新工具
void disRefreshCotlrs() {
tabs.clear();
if (refreshCotlrs.isEmpty) return;
for (var e in refreshCotlrs) {
e.dispose();
}
refreshCotlrs.clear();
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 10.h),
child: Align(
alignment: Alignment.centerLeft,
child: TabBar(
isScrollable: true,
controller: tabController,
indicatorColor: Colors.white,
unselectedLabelStyle: TextStyle(fontSize: 14.sp, color: const Color.fromRGBO(173, 191, 255, 1)),
labelStyle: TextStyle(fontSize: 16.sp, color: const Color.fromRGBO(148, 163, 182, 1)),
labelColor: Colors.white,
indicatorSize: TabBarIndicatorSize.label,
onTap: (index) {
bool flagTbRsh = tabRefresh[index];
setState(() {
_tabIndex = index;
if (!flagTbRsh) {
refreshCotlrs[index].callRefresh();
tabRefresh[index] = true;
}
});
},
tabs: tabs,
),
),
),
Expanded(
child: IndexedStack(
index: _tabIndex,
children: <Widget>[
...tabs.asMap().keys.map((theIdx) {
EasyRefreshController controller = refreshCotlrs[theIdx];
ReviewPageParams param = refreshParams[theIdx];
List<ReviewItem> reviewItems = refreshDatas[theIdx];
return EasyRefresh(
firstRefresh: theIdx == 0,
taskIndependence: true,
enableControlFinishLoad: true,
enableControlFinishRefresh: true,
header: MaterialHeader(),
footer: TaurusFooter(),
emptyWidget: reviewItems.isEmpty ? const MyEmptyWidget() : null,
controller: controller,
child: ListView.builder(
padding: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 16.h),
itemBuilder: (context, index) =>
ReviewItemView(markingUserId, reviewItems[index], widget.examSubjectId),
itemCount: reviewItems.length,
),
onRefresh: () async {
param.page = 1;
List<ReviewItem>? data = await toGetPageData(controller, param, isReFresh: true);
if (data != null) {
reviewItems.clear();
setState(() => reviewItems.addAll(data));
}
},
onLoad: () async {
param.page += 1;
List<ReviewItem>? data = await toGetPageData(controller, param);
if (data != null && data.isNotEmpty) {
setState(() => reviewItems.addAll(data));
return;
}
param.page -= 1;
},
);
}).toList()
],
),
),
],
);
}
}
// 赛选条件抽屉
class ConditionsDrawer extends StatefulWidget {
final AdditionalConditionsForReview condition;
final CollationCallback callback;
const ConditionsDrawer({required this.callback, required this.condition, Key? key}) : super(key: key);
@override
State<ConditionsDrawer> createState() => _ConditionsDrawerState();
}
class _ConditionsDrawerState extends State<ConditionsDrawer> {
late StreamSubscription<bool> keyboardSubscription;
late TextEditingController _inputController;
late FocusNode _inputFocusNode;
int collationIndex = -1; // 排序方式选中下标
@override
void initState() {
super.initState();
AdditionalConditionsForReview condition = widget.condition;
CollationItem? orderItem = condition.orderItem;
if (orderItem != null) {
toUpState(
setState, () => collationIndex = AdditionalConditionsForReview.collationArr.indexOf(orderItem), mounted);
}
_inputController = TextEditingController(text: condition.questionId);
_inputFocusNode = FocusNode()..addListener(_theSetState);
// 软键盘监听
keyboardSubscription = KeyboardVisibilityController().onChange.listen((bool visible) {
debugPrint('Keyboard visibility update. Is visible: $visible');
if (!visible) _inputFocusNode.unfocus();
});
}
@override
void dispose() {
super.dispose();
keyboardSubscription.cancel();
_inputController.dispose();
_inputFocusNode
..removeListener(_theSetState)
..dispose();
}
void _theSetState({VoidCallback? call}) {
toUpState(setState, () {
if (call != null) call();
}, mounted);
}
// 设置排序规则
void choiceOrderField(CollationItem item, int index) {
if (_inputFocusNode.hasFocus) {
_inputFocusNode.unfocus();
}
_theSetState(call: () => collationIndex = index);
}
@override
Widget build(BuildContext context) {
return Container(
width: 270.w,
color: Colors.white,
child: Column(
children: [
Expanded(
child: Container(
width: double.infinity,
height: double.infinity,
padding: EdgeInsets.only(
left: 20.w,
right: 30.h,
bottom: 30.h,
top: MediaQuery.of(context).padding.top + 20.h,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
getPaperIDInput(),
getCollation(),
],
),
),
),
Padding(
padding: EdgeInsets.symmetric(
horizontal: 10.w,
vertical: 20.h,
),
child: getBtn(),
),
],
),
);
}
// 确认、重置按钮
Widget getBtn() {
return Row(
children: [
Expanded(
child: MaterialButton(
height: 40.h,
splashColor: Colors.pink,
// color: Colors.blueGrey,
color: Colors.grey[100],
textColor: const Color.fromRGBO(102, 102, 102, 1),
minWidth: MediaQuery.of(context).size.width / 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: const Text(
'重置',
),
onPressed: () {
if (_inputFocusNode.hasFocus) {
_inputFocusNode.unfocus();
}
widget.callback(reset: false).then((_) => Navigator.pop(context));
},
),
),
SizedBox(width: 15.w),
Expanded(
flex: 2,
child: MaterialButton(
height: 40.h,
splashColor: Colors.pink,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: const Text(
'确定',
),
onPressed: () {
if (_inputFocusNode.hasFocus) {
_inputFocusNode.unfocus();
}
widget
.callback(
reset: false,
item: collationIndex > -1 ? AdditionalConditionsForReview.collationArr[collationIndex] : null,
testId: _inputController.text)
.then((_) => Navigator.pop(context));
}),
),
],
);
}
// 试题ID输入框
Widget getPaperIDInput() {
return Container(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
quickText('试题ID', size: 18.sp),
SizedBox(height: 10.h),
TextField(
controller: _inputController,
focusNode: _inputFocusNode,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
inputFormatters: [FilteringTextInputFormatter.digitsOnly], //数字,只能是整数
style: TextStyle(color: Colors.black45, fontSize: 14.sp),
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey[300]!),
),
hintText: '请输入答卷ID',
hintStyle: TextStyle(color: Colors.grey[300], fontSize: 14.sp),
suffixIcon: !_inputFocusNode.hasFocus
? null
: IconButton(
icon: Icon(Icons.highlight_off, size: 14.sp, color: Colors.grey),
onPressed: () {
_inputController.clear();
},
),
),
)
],
),
);
}
// 排序规则
Widget getCollation() {
return Container(
margin: EdgeInsets.only(top: 50.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
quickText('试题ID', size: 18.sp),
SizedBox(height: 20.h),
...AdditionalConditionsForReview.collationArr.asMap().keys.map((iIndex) {
CollationItem item = AdditionalConditionsForReview.collationArr[iIndex];
bool flag = iIndex == collationIndex;
return Container(
margin: EdgeInsets.symmetric(vertical: 6.h),
width: double.infinity,
height: 34.h,
alignment: Alignment.center,
decoration: BoxDecoration(
color: flag ? Colors.blue[50] : Colors.grey[100],
borderRadius: BorderRadius.all(Radius.circular(4.sp)),
),
child: Row(
children: [
Expanded(
child: TextButton(
onPressed: () => choiceOrderField(item, iIndex),
child: quickText(
item.title,
size: 14.sp,
color: flag ? Theme.of(context).primaryColor : Colors.black54,
),
),
)
],
),
);
// return Container(
// margin: EdgeInsets.symmetric(vertical: 6.h),
// width: double.infinity,
// height: 34.h,
// alignment: Alignment.center,
// decoration: BoxDecoration(
// color: flag ? Colors.blue[50] : Colors.grey[200],
// borderRadius: BorderRadius.all(Radius.circular(4.sp))
// ),
// child: quickText(item.title,
// size: 15.sp, color: flag ? Theme.of(context).primaryColor : Colors.black54),
// );
}).toList()
],
),
);
}
}
// 底层头部区域
class ReviewHeadBox extends StatelessWidget {
const ReviewHeadBox({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
height: double.infinity,
child: Column(
children: [
Container(
height: 200.h,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/personal_bgi.png'),
fit: BoxFit.cover,
),
),
),
Expanded(
child: Container(
color: const Color.fromRGBO(248, 248, 248, 1),
))
],
),
);
}
}