feat: 优化口语防重复问答、课程推进流转、键盘输入流畅度与竖屏锁定
- AI口语约束:在对话系统提示词与单轮指导中增加防重复问答约束,避免重复寒暄及索取已提供信息,根据历史自然向前推进 - 课程流转:修复多段课程学完后今日任务卡片流转下一课,及退出重进定位逻辑 - 竖屏锁定:在 Flutter、Android 及 iOS 平台配置仅支持竖屏显示 - 对话交互:优化对话界面文本输入重绘与词汇查询,解决键盘输入卡顿 - 测试用例:补充并更新单轮约束、状态流转与返回键退出测试,212项测试全绿
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user