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
@@ -6,15 +6,23 @@ class _SpeakingStep extends StatefulWidget {
required this.segmentId,
required this.text,
this.tip = '',
this.chunks = const [],
this.examples = const [],
required this.buildIndex,
required this.onBuildProgress,
required this.keepRecording,
required this.onContinue,
});
final String segmentId;
final String text;
final String tip;
final List<String> chunks;
final List<String> examples;
final int buildIndex;
final ValueChanged<int> onBuildProgress;
final AppState state;
final bool keepRecording;
final VoidCallback onContinue;
final ValueChanged<bool> onContinue;
@override
State<_SpeakingStep> createState() => _SpeakingStepState();
@@ -89,12 +97,57 @@ class _SpeakingStepState extends State<_SpeakingStep>
if (widget.tip.isNotEmpty)
SectionCard(
tint: AppColors.warm,
child: Text(
widget.tip,
style: TextStyle(color: AppColors.warmInk),
),
child: Text(widget.tip, style: TextStyle(color: AppColors.warmInk)),
),
SegmentUsage(segmentId: widget.segmentId, state: widget.state),
if (widget.chunks.length >= 2)
SectionCard(
tint: AppColors.surfaceMuted,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('拆开练(可选)'),
const SizedBox(height: 8),
Text(
widget.buildIndex == 0
? '先听词块,再逐步合成整句。'
: widget.chunks.take(widget.buildIndex).join(' '),
),
TextButton.icon(
onPressed: () async {
final next = widget.buildIndex < widget.chunks.length
? widget.buildIndex + 1
: 1;
await VoiceService.instance.speak(
widget.chunks.take(next).join(' '),
);
widget.onBuildProgress(next);
},
icon: const Icon(Icons.play_arrow),
label: Text(
widget.buildIndex == widget.chunks.length
? '从头再练'
: '听下一段并跟读',
),
),
],
),
),
if (widget.examples.isNotEmpty)
ExpansionTile(
title: Text('更多示范句(${widget.examples.length})'),
children: [
for (final example in widget.examples)
ListTile(
title: LexiconText(example, state: widget.state),
trailing: IconButton(
tooltip: '播放示范句',
icon: const Icon(Icons.volume_up_outlined),
onPressed: () => VoiceService.instance.speak(example),
),
),
],
),
SecondaryButton(
label: transcribing
? '正在 AI 识别发音…'
@@ -146,8 +199,10 @@ class _SpeakingStepState extends State<_SpeakingStep>
),
),
PrimaryButton(
label: transcript.isEmpty ? '我已跟读,继续' : '确认并继续',
onPressed: widget.onContinue,
label: recordingPath == null ? '跳过跟读,继续' : '录音完成,继续',
onPressed: (listening || transcribing)
? null
: () => widget.onContinue(recordingPath != null),
),
],
),