feat: 增加AI配置文件与TTS自动朗读,修复二级页面返回按钮与音频播放
This commit is contained in:
@@ -101,7 +101,7 @@ class _LessonFlowState extends State<LessonFlow> {
|
||||
state: widget.state,
|
||||
initialText: activity.listening,
|
||||
),
|
||||
onContinue: listeningAudioPlayed && selectedAnswer == 0
|
||||
onContinue: selectedAnswer == 0
|
||||
? widget.state.completeListening
|
||||
: null,
|
||||
);
|
||||
@@ -192,6 +192,7 @@ class _LessonFlowState extends State<LessonFlow> {
|
||||
return _LessonScope(
|
||||
title:
|
||||
'第 ${lesson.number} 课 · ${lesson.title} · 第 ${widget.state.activeSegmentIndexFor(lesson.id) + 1}/${lesson.segments.length} 段',
|
||||
onExit: widget.onFinish,
|
||||
child: content,
|
||||
);
|
||||
}
|
||||
@@ -205,7 +206,21 @@ class _LessonScaffold extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => AppPage(
|
||||
appBar: AppBar(title: Text(_LessonScope.of(context))),
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
tooltip: "退出课程",
|
||||
onPressed: () {
|
||||
final onExit = _LessonScope.exitOf(context);
|
||||
if (onExit != null) {
|
||||
onExit();
|
||||
} else if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
title: Text(_LessonScope.of(context)),
|
||||
),
|
||||
child: SpacedColumn(
|
||||
spacing: 16,
|
||||
children: [
|
||||
@@ -231,16 +246,25 @@ class _LessonScaffold extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _LessonScope extends InheritedWidget {
|
||||
const _LessonScope({required this.title, required super.child});
|
||||
const _LessonScope({
|
||||
required this.title,
|
||||
this.onExit,
|
||||
required super.child,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final VoidCallback? onExit;
|
||||
|
||||
static String of(BuildContext context) =>
|
||||
context.dependOnInheritedWidgetOfExactType<_LessonScope>()?.title ??
|
||||
'A0 课程练习';
|
||||
|
||||
static VoidCallback? exitOf(BuildContext context) =>
|
||||
context.dependOnInheritedWidgetOfExactType<_LessonScope>()?.onExit;
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(_LessonScope oldWidget) => title != oldWidget.title;
|
||||
bool updateShouldNotify(_LessonScope oldWidget) =>
|
||||
title != oldWidget.title || onExit != oldWidget.onExit;
|
||||
}
|
||||
|
||||
class _PreviewStep extends StatelessWidget {
|
||||
@@ -357,7 +381,12 @@ class _ListeningStep extends StatelessWidget {
|
||||
for (var index = 0; index < answers.length; index++)
|
||||
SectionCard(
|
||||
tint: selectedAnswer == index ? AppColors.softGreen : null,
|
||||
onTap: audioPlayed ? () => onSelected(index) : null,
|
||||
onTap: () {
|
||||
onSelected(index);
|
||||
if (!audioPlayed) {
|
||||
onPlayed();
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
@@ -374,7 +403,9 @@ class _ListeningStep extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
PrimaryButton(
|
||||
label: audioPlayed ? '检查并继续' : '先播放音频',
|
||||
label: selectedAnswer >= 0
|
||||
? '检查并继续'
|
||||
: (audioPlayed ? '请选择答案' : '先播放音频或选择答案'),
|
||||
onPressed: onContinue,
|
||||
),
|
||||
if (selectedAnswer >= 0 && selectedAnswer != 0)
|
||||
@@ -1126,16 +1157,38 @@ class _AudioRow extends StatelessWidget {
|
||||
children: [
|
||||
IconButton.filled(
|
||||
onPressed: () async {
|
||||
await VoiceService.instance.speak(speech ?? label);
|
||||
onPlayed?.call();
|
||||
try {
|
||||
await VoiceService.instance.speak(speech ?? label);
|
||||
} finally {
|
||||
onPlayed?.call();
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.play_arrow),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(child: Text(label)),
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
try {
|
||||
await VoiceService.instance.speak(speech ?? label);
|
||||
} finally {
|
||||
onPlayed?.call();
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Text(label),
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () =>
|
||||
VoiceService.instance.speak(speech ?? label, slow: true),
|
||||
onPressed: () async {
|
||||
try {
|
||||
await VoiceService.instance.speak(speech ?? label, slow: true);
|
||||
} finally {
|
||||
onPlayed?.call();
|
||||
}
|
||||
},
|
||||
child: const Text('慢速'),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user