137 lines
4.3 KiB
Dart
137 lines
4.3 KiB
Dart
/// ============================================================
|
||
/// 【课堂习题 · 会话状态与 UI 桥接】
|
||
///
|
||
/// Vue 端各 createXxxHandlers 通过一堆 ref 共享状态;Flutter 端按
|
||
/// 用户拍板用“普通 Dart 控制器(无 Riverpod)”,因此把跨控制器共享
|
||
/// 的可变状态收敛到 [ExerciseSession],把与 Widget/导航/弹窗相关的
|
||
/// 副作用收敛到 [ExerciseUiBridge],由页面 State 实现并注入。
|
||
/// ============================================================
|
||
library;
|
||
|
||
import 'package:flutter/widgets.dart';
|
||
import 'package:new_learn/features/conquerPackage/classExercises/exercise_question_view.dart';
|
||
import 'package:new_learn/features/conquerPackage/classExercises/models/question_model.dart';
|
||
import 'package:new_learn/features/conquerPackage/classExercises/widgets/compound_question_panel.dart';
|
||
import 'package:new_learn/features/conquerPackage/classExercises/widgets/question_plan_countdown.dart';
|
||
|
||
/// 当前本地 ISO 时间(对标 getCurrentISO)
|
||
String getCurrentISO() => DateTime.now().toIso8601String();
|
||
|
||
/// 攻克流程弹窗配置(对标 Vue conquerPopupCon)
|
||
class ConquerPopupConfig {
|
||
ConquerPopupConfig({
|
||
this.show = false,
|
||
this.type = 'note',
|
||
this.title = '',
|
||
this.subtitle = '',
|
||
this.confirmText = '确定',
|
||
this.cancelText = '取消',
|
||
this.isConfirm = false,
|
||
this.confirmAction = '',
|
||
});
|
||
|
||
bool show;
|
||
String type; // note / time / submit / grading
|
||
String title;
|
||
String subtitle;
|
||
String confirmText;
|
||
String cancelText;
|
||
bool isConfirm;
|
||
String confirmAction;
|
||
}
|
||
|
||
/// 跨控制器共享的会话状态(字段名与 Vue 保持一致)
|
||
class ExerciseSession {
|
||
/// 题目列表(解析后的 QuestionModel,与 Map 同引用语义)
|
||
List<QuestionModel> questions = [];
|
||
|
||
/// 当前大题索引(swiper index)
|
||
int currentIndex = 0;
|
||
|
||
/// edit / view
|
||
String mode = 'edit';
|
||
|
||
String courseId = '';
|
||
String packId = '';
|
||
String totalPackId = '';
|
||
String recordId = '';
|
||
|
||
bool isWrong = false;
|
||
|
||
/// view 全量题号(originalMaxSort)
|
||
int originalMaxSort = 0;
|
||
|
||
/// 导航栏标题
|
||
String navBarTitle = '';
|
||
|
||
/// 大题索引 -> 激活子题索引
|
||
final Map<int, int> activeSubIndexMap = {};
|
||
|
||
/// 大题索引 -> 等待页面挂载后定位的子题索引
|
||
final Map<int, int> pendingSubIndexMap = {};
|
||
|
||
/// 已处理过的题目 id(避免重复提交,对应 processedQuestionIds)
|
||
final Set<String> processedQuestionIds = <String>{};
|
||
|
||
/// 单题页 ref(itemRefs)
|
||
final Map<int, GlobalKey<ExerciseQuestionViewState>> itemRefs = {};
|
||
|
||
/// 复合题面板 ref(compoundQuestionFn)
|
||
GlobalKey<CompoundQuestionPanelState>? panelKey;
|
||
|
||
/// 倒计时组件 ref(countDownRef)
|
||
GlobalKey<QuestionPlanCountDownState>? countDownKey;
|
||
|
||
bool get isView => mode == 'view';
|
||
|
||
/// 当前题(questionsInfo)。Flutter 与列表元素保持同一引用,
|
||
/// 不再做 Vue 的浅拷贝。
|
||
QuestionModel? get currentQuestion {
|
||
if (currentIndex < 0 || currentIndex >= questions.length) return null;
|
||
return questions[currentIndex];
|
||
}
|
||
}
|
||
|
||
/// 控制器 -> 页面的副作用桥接(页面 State implements 本接口)
|
||
abstract class ExerciseUiBridge {
|
||
/// 全局 loading(对标 uni.showLoading({mask:true}))
|
||
void showLoading([String message]);
|
||
void hideLoading();
|
||
|
||
/// 轻提示(对标 uni.showToast icon:none)
|
||
void toast(String message);
|
||
|
||
/// 通知页面刷新(对标 Vue ref 赋值触发渲染)
|
||
void notifyListeners();
|
||
|
||
/// PageController 切页(对标 swiper.slideTo)
|
||
void jumpToPage(int index);
|
||
|
||
/// 打开“退出攻克”确认框(openExitPopup)
|
||
void openExitPopup();
|
||
|
||
/// 展示攻克流程弹窗(getConquerPopupCon)
|
||
void showConquerPopup(ConquerPopupConfig config);
|
||
|
||
/// 关闭攻克流程弹窗
|
||
void hideConquerPopup();
|
||
|
||
/// 答题卡显隐(showAnswerSheet)
|
||
void setAnswerSheetVisible(bool visible);
|
||
|
||
/// 暂停弹窗显隐(showPausePopup)
|
||
void setPausePopupVisible(bool visible);
|
||
|
||
/// 交卷成功跳报告页(redirectTo report)
|
||
void redirectToReport(String recordId, String packId);
|
||
|
||
/// 返回上 N 页(navigateBack delta)
|
||
void navigateBack({int delta});
|
||
|
||
/// 重启到攻克列表(reLaunch captureList)
|
||
void relaunchCaptureList(String courseId);
|
||
|
||
/// 是否平板(对标 isPadDevice(),time 弹窗字号用)
|
||
bool get isPad;
|
||
}
|