更新
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
/// ============================================================
|
||||
/// 【课堂习题 · 顶部计时与暂停联动】
|
||||
///
|
||||
/// 1:1 迁移 Vue exerciseTiming.js(createTimingHandlers):
|
||||
/// - 顶部计时(可暂停 / 继续),与倒计时组件联动;
|
||||
/// - 按 recordId 缓存 / 恢复计时进度(实际逻辑按题目 answerTimeSecond
|
||||
/// 汇总,SP 缓存代码在 Vue 端已注释,这里仍保留 persist 写缓存);
|
||||
/// - 暂停时统计剩余未答题数,驱动暂停弹窗。
|
||||
/// ============================================================
|
||||
library;
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:new_learn/features/conquerPackage/classExercises/controllers/exercise_session.dart';
|
||||
import 'package:new_learn/features/conquerPackage/classExercises/controllers/exercise_submit.dart';
|
||||
import 'package:new_learn/features/conquerPackage/classExercises/controllers/exercise_timing.dart';
|
||||
|
||||
class ExerciseTimingHandlers {
|
||||
ExerciseTimingHandlers({
|
||||
required this.session,
|
||||
required this.bridge,
|
||||
required this.submit,
|
||||
required this.isPausePopupVisible,
|
||||
this.onUiTick,
|
||||
}) {
|
||||
timing = ExerciseTiming(onTick: (tick) {
|
||||
final total = timingOffsetMs + tick.elapsedMs;
|
||||
timingText = formatElapsed(total);
|
||||
timingRunning = tick.running;
|
||||
// 页面层据此刷新顶部计时胶囊(对标 Vue ref 赋值触发渲染)
|
||||
onUiTick?.call();
|
||||
});
|
||||
}
|
||||
|
||||
final ExerciseSession session;
|
||||
final ExerciseUiBridge bridge;
|
||||
final ExerciseSubmitHandlers submit;
|
||||
|
||||
/// 每次秒表 tick 后回调页面刷新 UI(计时文案)
|
||||
final void Function()? onUiTick;
|
||||
|
||||
/// 暂停弹窗当前是否展示(对标 showPausePopup.value 读取)
|
||||
final bool Function() isPausePopupVisible;
|
||||
|
||||
late final ExerciseTiming timing;
|
||||
|
||||
/// 顶部计时文案
|
||||
String timingText = '00:00';
|
||||
|
||||
/// 计时是否运行中
|
||||
bool timingRunning = true;
|
||||
|
||||
/// 累计偏移(恢复的已学时长)
|
||||
int timingOffsetMs = 0;
|
||||
|
||||
/// 暂停时剩余未答题数
|
||||
int remainingCount = 0;
|
||||
|
||||
/// 恢复计时缓存(Vue 端实际使用题目 answerTimeSecond 汇总)
|
||||
void restoreTimingCache() {
|
||||
try {
|
||||
var totalStudyTime = 0;
|
||||
for (final item in session.questions) {
|
||||
totalStudyTime += item.answerTimeSecond;
|
||||
}
|
||||
if (totalStudyTime != 0) {
|
||||
timingOffsetMs = totalStudyTime * 1000;
|
||||
timingText = formatElapsed(totalStudyTime * 1000);
|
||||
} else {
|
||||
timingOffsetMs = 0;
|
||||
timingText = '00:00';
|
||||
}
|
||||
} catch (_) {
|
||||
timingOffsetMs = 0;
|
||||
timingText = '00:00';
|
||||
}
|
||||
}
|
||||
|
||||
/// 持久化计时进度(view 模式 / 无 recordId 不写)
|
||||
Future<void> persistTimingCache() async {
|
||||
final keyId = session.recordId;
|
||||
if (session.isView || keyId.isEmpty) return;
|
||||
final elapsedMs = timingOffsetMs + timing.getElapsedMs();
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(
|
||||
'classExercises_timing_$keyId',
|
||||
// 与 Vue 一致:历史上存的是 JSON 字符串
|
||||
'{"elapsedMs":$elapsedMs}',
|
||||
);
|
||||
} catch (_) {
|
||||
// 忽略落盘失败
|
||||
}
|
||||
}
|
||||
|
||||
/// 切换计时(对标 toggleTiming)
|
||||
void toggleTiming() {
|
||||
if (isPausePopupVisible()) return;
|
||||
if (timing.isRunning()) {
|
||||
timing.pause();
|
||||
session.countDownKey?.currentState?.pause();
|
||||
bridge.setPausePopupVisible(true);
|
||||
|
||||
final list = submit.batchProcessQuestions();
|
||||
remainingCount =
|
||||
list.where((res) => res['answered'] != true).length;
|
||||
bridge.notifyListeners();
|
||||
return;
|
||||
}
|
||||
timing.resume();
|
||||
session.countDownKey?.currentState?.start();
|
||||
}
|
||||
|
||||
/// 关闭暂停弹窗并继续(对标 handlePausePopupClose)
|
||||
void handlePausePopupClose() {
|
||||
bridge.setPausePopupVisible(false);
|
||||
if (!timing.isRunning()) timing.resume();
|
||||
session.countDownKey?.currentState?.start();
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
timing.stop();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user