feat: 优化口语防重复问答、课程推进流转、键盘输入流畅度与竖屏锁定
- AI口语约束:在对话系统提示词与单轮指导中增加防重复问答约束,避免重复寒暄及索取已提供信息,根据历史自然向前推进 - 课程流转:修复多段课程学完后今日任务卡片流转下一课,及退出重进定位逻辑 - 竖屏锁定:在 Flutter、Android 及 iOS 平台配置仅支持竖屏显示 - 对话交互:优化对话界面文本输入重绘与词汇查询,解决键盘输入卡顿 - 测试用例:补充并更新单轮约束、状态流转与返回键退出测试,212项测试全绿
This commit is contained in:
@@ -682,18 +682,20 @@ class _DialoguePageState extends State<DialoguePage>
|
||||
child: SpacedColumn(
|
||||
children: [
|
||||
Eyebrow('目标:${script.goal}'),
|
||||
Container(
|
||||
constraints: const BoxConstraints(minHeight: 250),
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: turns.length,
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 10),
|
||||
itemBuilder: (context, index) => _TurnBubble(
|
||||
turn: turns[index],
|
||||
state: widget.state,
|
||||
showTranslation: _shownTranslations.contains(index),
|
||||
onToggleTranslation: () => _toggleTurnTranslation(index),
|
||||
RepaintBoundary(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(minHeight: 250),
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: turns.length,
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 10),
|
||||
itemBuilder: (context, index) => _TurnBubble(
|
||||
turn: turns[index],
|
||||
state: widget.state,
|
||||
showTranslation: _shownTranslations.contains(index),
|
||||
onToggleTranslation: () => _toggleTurnTranslation(index),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -750,13 +752,22 @@ class _DialoguePageState extends State<DialoguePage>
|
||||
),
|
||||
TextField(
|
||||
controller: controller,
|
||||
onChanged: (value) => setState(() {
|
||||
if (usedVoice && value != lastTranscript) {
|
||||
transcriptEdited = true;
|
||||
onChanged: (value) {
|
||||
final needResetVoice =
|
||||
usedVoice &&
|
||||
value != lastTranscript &&
|
||||
!transcriptEdited;
|
||||
final needClearAi = aiCheck != null || aiCheckError != null;
|
||||
if (needResetVoice || needClearAi) {
|
||||
setState(() {
|
||||
if (needResetVoice) transcriptEdited = true;
|
||||
if (needClearAi) {
|
||||
aiCheck = null;
|
||||
aiCheckError = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
aiCheck = null;
|
||||
aiCheckError = null;
|
||||
}),
|
||||
},
|
||||
onSubmitted: (_) => send(),
|
||||
decoration: InputDecoration(
|
||||
hintText: '输入你的英文回答',
|
||||
@@ -785,21 +796,27 @@ class _DialoguePageState extends State<DialoguePage>
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed:
|
||||
controller.text.trim().isEmpty ||
|
||||
checkingWithAi ||
|
||||
waitingForReply
|
||||
? null
|
||||
: _checkWithAi,
|
||||
icon: checkingWithAi
|
||||
? const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.spellcheck),
|
||||
label: Text(checkingWithAi ? '正在检查…' : '发送前 AI 检查语法和拼写(可选)'),
|
||||
ValueListenableBuilder<TextEditingValue>(
|
||||
valueListenable: controller,
|
||||
builder: (context, value, _) {
|
||||
final canCheck =
|
||||
value.text.trim().isNotEmpty &&
|
||||
!checkingWithAi &&
|
||||
!waitingForReply;
|
||||
return OutlinedButton.icon(
|
||||
onPressed: canCheck ? _checkWithAi : null,
|
||||
icon: checkingWithAi
|
||||
? const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.spellcheck),
|
||||
label: Text(
|
||||
checkingWithAi ? '正在检查…' : '发送前 AI 检查语法和拼写(可选)',
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (aiCheck != null)
|
||||
SectionCard(
|
||||
|
||||
Reference in New Issue
Block a user