feat: 优化口语防重复问答、课程推进流转、键盘输入流畅度与竖屏锁定

- AI口语约束:在对话系统提示词与单轮指导中增加防重复问答约束,避免重复寒暄及索取已提供信息,根据历史自然向前推进
- 课程流转:修复多段课程学完后今日任务卡片流转下一课,及退出重进定位逻辑
- 竖屏锁定:在 Flutter、Android 及 iOS 平台配置仅支持竖屏显示
- 对话交互:优化对话界面文本输入重绘与词汇查询,解决键盘输入卡顿
- 测试用例:补充并更新单轮约束、状态流转与返回键退出测试,212项测试全绿
This commit is contained in:
shen
2026-09-18 07:38:54 -07:00
parent 0a4f97d105
commit 5b9cba793e
16 changed files with 568 additions and 242 deletions
@@ -71,6 +71,9 @@ class _LearningShellState extends State<LearningShell> {
void handleBack() {
switch (route) {
case _ShellRoute.lesson:
if (widget.state.lessonStep == LessonStep.complete) {
widget.state.finishCurrentLessonSegment();
}
showTab(tab);
case _ShellRoute.dialogue:
if (dialogueInLesson) {
@@ -99,66 +102,6 @@ class _LearningShellState extends State<LearningShell> {
@override
Widget build(BuildContext context) {
Widget body;
switch (route) {
case _ShellRoute.lesson:
body = LessonFlow(
// 换课或换段时重建,避免上一段的听力选择和播放状态沿用过来。
key: ValueKey(
'${widget.state.activeLessonId}-'
'${widget.state.activeSegmentIndexFor(widget.state.activeLessonId)}',
),
state: widget.state,
onOpenDialogue: () => showDialogue(inLesson: true),
onFinish: () => showTab(tab),
);
case _ShellRoute.dialogue:
body = DialoguePage(
key: ValueKey(dialogueInLesson ? 'lesson' : 'scene-$sceneId'),
state: widget.state,
isLessonDialogue: dialogueInLesson,
sceneId: sceneId,
onFinished: (summary) {
if (dialogueInLesson) {
showLesson();
} else if (summary != null) {
showSummary(summary);
} else {
handleBack();
}
},
);
case _ShellRoute.summary:
body = DialogueSummaryPage(
summary: dialogueSummary!,
onHome: () => showTab(AppTab.learn),
onLesson: showLesson,
onRetry: () => showDialogue(scene: sceneId),
onBack: () => showTab(tab),
);
case _ShellRoute.adaptiveLesson:
body = AdaptiveLessonPage(
state: widget.state,
onFinished: () => showTab(AppTab.review),
);
case _ShellRoute.assessment:
body = AssessmentPage(
state: widget.state,
pack: assessmentPack!,
onFinished: () => showTab(AppTab.profile),
onStartReplacement: showAssessment,
);
case _ShellRoute.assessmentPreparation:
body = AssessmentPreparationPage(
state: widget.state,
pack: assessmentPack!,
onStart: startAssessment,
onBack: () => showTab(AppTab.profile),
);
case _ShellRoute.tab:
body = _tabContent();
}
return PopScope(
canPop: route == _ShellRoute.tab && tab == AppTab.learn,
onPopInvokedWithResult: (didPop, _) {
@@ -168,8 +111,69 @@ class _LearningShellState extends State<LearningShell> {
},
child: AnimatedBuilder(
animation: widget.state,
builder: (context, _) => Scaffold(
body: body,
builder: (context, _) {
final Widget body;
switch (route) {
case _ShellRoute.lesson:
body = LessonFlow(
// 换课或换段时重建,避免上一段的听力选择和播放状态沿用过来。
key: ValueKey(
'${widget.state.activeLessonId}-'
'${widget.state.activeSegmentIndexFor(widget.state.activeLessonId)}',
),
state: widget.state,
onOpenDialogue: () => showDialogue(inLesson: true),
onFinish: () => showTab(tab),
);
case _ShellRoute.dialogue:
body = DialoguePage(
key: ValueKey(dialogueInLesson ? 'lesson' : 'scene-$sceneId'),
state: widget.state,
isLessonDialogue: dialogueInLesson,
sceneId: sceneId,
onFinished: (summary) {
if (dialogueInLesson) {
showLesson();
} else if (summary != null) {
showSummary(summary);
} else {
handleBack();
}
},
);
case _ShellRoute.summary:
body = DialogueSummaryPage(
summary: dialogueSummary!,
onHome: () => showTab(AppTab.learn),
onLesson: showLesson,
onRetry: () => showDialogue(scene: sceneId),
onBack: () => showTab(tab),
);
case _ShellRoute.adaptiveLesson:
body = AdaptiveLessonPage(
state: widget.state,
onFinished: () => showTab(AppTab.review),
);
case _ShellRoute.assessment:
body = AssessmentPage(
state: widget.state,
pack: assessmentPack!,
onFinished: () => showTab(AppTab.profile),
onStartReplacement: showAssessment,
);
case _ShellRoute.assessmentPreparation:
body = AssessmentPreparationPage(
state: widget.state,
pack: assessmentPack!,
onStart: startAssessment,
onBack: () => showTab(AppTab.profile),
);
case _ShellRoute.tab:
body = _tabContent();
}
return Scaffold(
body: body,
bottomNavigationBar: route == _ShellRoute.tab
? NavigationBar(
selectedIndex: tab.index,
@@ -201,7 +205,8 @@ class _LearningShellState extends State<LearningShell> {
],
)
: null,
),
);
},
),
);
}
@@ -211,7 +216,14 @@ class _LearningShellState extends State<LearningShell> {
case AppTab.learn:
return HomePage(
state: widget.state,
onStartLesson: showLesson,
onStartLesson: () {
if (widget.state.completedLessonIds
.contains(widget.state.activeLessonId)) {
final next = widget.state.nextIncompleteLessonId;
if (next != null) widget.state.openLesson(next);
}
showLesson();
},
onOpenLesson: (id) {
widget.state.openLesson(id);
showLesson();