feat: 完善听力口语辅助状态记录、练习分块与复习抽查间隔机制

This commit is contained in:
shen
2026-09-23 18:33:22 -07:00
parent 6b42b7abc3
commit d740a9e19c
106 changed files with 2933 additions and 845 deletions
@@ -30,6 +30,18 @@ class _LearningRecordsViewState extends State<LearningRecordsView> {
@override
Widget build(BuildContext context) {
final progress = <String>{};
final improved = <String, AttemptEvidence>{};
for (final entry in widget.state.attemptEvidence) {
final key = '${entry.itemId}|${entry.skill}';
if (entry.outcome == EvidenceKind.assisted ||
entry.outcome == EvidenceKind.languageError) {
progress.add(key);
} else if (entry.outcome == EvidenceKind.independentSuccess &&
progress.contains(key)) {
improved[key] = entry;
}
}
final records = widget.state.attemptEvidence.reversed
.where(_matches)
.take(_limit)
@@ -41,6 +53,21 @@ class _LearningRecordsViewState extends State<LearningRecordsView> {
return SpacedColumn(
spacing: 16,
children: [
if (improved.isNotEmpty)
SectionCard(
tint: AppColors.softGreen,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('从需要帮助到独立完成'),
const SizedBox(height: 6),
for (final entry in improved.values.toList().reversed.take(3))
Text(
'${widget.state.mastery[entry.itemId]?.label ?? entry.itemId} · ${entry.skill}',
),
],
),
),
SizedBox(
width: double.infinity,
child: SegmentedButton<_RecordFilter>(
@@ -100,7 +127,7 @@ class _RecordTile extends StatelessWidget {
),
EvidenceKind.assisted => ('带提示', AppColors.warmInk, AppColors.warm),
EvidenceKind.languageError => ('需要复核', AppColors.warmInk, AppColors.warm),
EvidenceKind.pending => ('稍后完成', AppColors.muted, AppColors.surfaceMuted),
EvidenceKind.pending => ('待确认', AppColors.muted, AppColors.surfaceMuted),
EvidenceKind.exposure => ('已查看', AppColors.muted, AppColors.surfaceMuted),
};
return ListTile(
@@ -110,7 +137,19 @@ class _RecordTile extends StatelessWidget {
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: Text('${evidence.skill} · ${relativeTime(evidence.createdAt)}'),
subtitle: Text(
[
evidence.skill,
relativeTime(evidence.createdAt),
if (evidence.listeningSupport == ListeningSupport.slow) '慢听后完成',
if (evidence.listeningSupport == ListeningSupport.transcript)
'看原文后完成',
if (evidence.firstTry == true) '首次答对',
if (evidence.attemptCount != null && evidence.attemptCount! > 1)
'尝试 ${evidence.attemptCount} 次',
if (evidence.usedSentenceBuild) '练过句子拆段',
].join(' · '),
),
trailing: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
@@ -22,7 +22,7 @@ class LevelProgressView extends StatelessWidget {
style: Theme.of(context).textTheme.headlineMedium,
),
Text(
state.a0Passed ? 'A1 主线内容尚未提供,可继续进行 A0 巩固。' : '进度来自掌握证据,不是上完固定课数就升级。',
state.a0Passed ? 'A1 课程待教学审校开放,可继续进行 A0 巩固。' : '进度来自掌握证据,不是上完固定课数就升级。',
),
MenuGroup(
children: [
@@ -306,7 +306,7 @@ class _StageCard extends StatelessWidget {
),
Text(
missing.isEmpty
? 'A0 已通过。A1 主线内容尚未提供,可继续进行 A0 巩固。'
? 'A0 已通过。A1 课程待教学审校开放,可继续进行 A0 巩固。'
: '还需:${missing.join(' · ')}',
style: Theme.of(context).textTheme.bodyMedium,
),