part of '../lesson_flow.dart'; class _ReadingStep extends StatefulWidget { const _ReadingStep({ required this.state, required this.activity, required this.onLookup, required this.onContinue, }); final LessonActivity activity; final AppState state; final VoidCallback onLookup; final VoidCallback onContinue; @override State<_ReadingStep> createState() => _ReadingStepState(); } class _ReadingStepState extends State<_ReadingStep> { final controller = TextEditingController(); int? selectedOptionIndex; bool showAnswer = false; /// 打乱后的选项:答案不再固定排在第一位,但同一道题顺序保持稳定。 late final List options = shuffledOptions( widget.activity.readingOptions, '${widget.activity.readingQuestion}-reading', ); @override void dispose() { controller.dispose(); super.dispose(); } bool _isOptionCorrect(int index) { if (options.isEmpty || index < 0 || index >= options.length) { return false; } final option = options[index].trim(); final answer = widget.activity.readingAnswer.trim(); if (option.toLowerCase() == answer.toLowerCase()) return true; final normOption = option.toLowerCase().replaceAll( RegExp(r'[^a-z0-9\u4e00-\u9fa5]'), '', ); final normAnswer = answer.toLowerCase().replaceAll( RegExp(r'[^a-z0-9\u4e00-\u9fa5]'), '', ); return normOption.isNotEmpty && normAnswer.isNotEmpty && (normOption.contains(normAnswer) || normAnswer.contains(normOption)); } bool get isOptionMode => options.isNotEmpty; bool get isCorrect { if (isOptionMode) { return selectedOptionIndex != null && _isOptionCorrect(selectedOptionIndex!); } final answer = widget.activity.readingAnswer.toLowerCase().replaceAll( RegExp(r'[^a-z0-9\u4e00-\u9fa5]'), '', ); final response = controller.text.toLowerCase().replaceAll( RegExp(r'[^a-z0-9\u4e00-\u9fa5]'), '', ); // 只接受写全了答案的输入:过去反向的 answer.contains(response) 让单个字母 // 也能判对('a' 通过 'A book')。 return response.isNotEmpty && answer.isNotEmpty && response.contains(answer); } @override Widget build(BuildContext context) { final hasSelected = selectedOptionIndex != null; final answeredCorrectly = isCorrect; return _LessonScaffold( step: 4, child: SpacedColumn( children: [ const Eyebrow('读一读'), Text('在对话里找到答案。', style: Theme.of(context).textTheme.headlineMedium), SectionCard( tint: AppColors.softGreen, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Icon( Icons.chat_bubble_outline, size: 16, color: AppColors.green, ), SizedBox(width: 6), Text( '对话内容', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.green, ), ), ], ), InkWell( onTap: () => VoiceService.instance.speak(widget.activity.reading), borderRadius: BorderRadius.circular(16), child: Padding( padding: EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), child: Row( children: [ Icon( Icons.volume_up_outlined, size: 16, color: AppColors.green, ), SizedBox(width: 4), Text( '朗读对话', style: TextStyle( fontSize: 13, color: AppColors.green, ), ), ], ), ), ), ], ), const SizedBox(height: 8), LexiconText( widget.activity.reading, state: widget.state, style: const TextStyle(fontSize: 16, height: 1.6), ), ], ), ), Row( children: [ TextButton.icon( onPressed: widget.onLookup, icon: const Icon(Icons.menu_book_outlined, size: 18), label: const Text('查词或短语'), ), const SizedBox(width: 8), TextButton.icon( onPressed: () { final lines = widget.activity.reading.split('\n'); final target = lines .firstWhere( (l) => l.trim().isNotEmpty, orElse: () => widget.activity.reading, ) .replaceFirst(RegExp(r'^[A-Za-z]+:\s*'), ''); showLexiconLookup( context, state: widget.state, initialText: target, ); }, icon: const Icon(Icons.auto_stories_outlined, size: 18), label: const Text('句型深度解析'), ), ], ), SectionCard( tint: AppColors.surfaceMuted, child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2, ), decoration: BoxDecoration( color: AppColors.green.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(6), ), child: Text( '问题', style: TextStyle( color: AppColors.green, fontWeight: FontWeight.bold, fontSize: 12, ), ), ), const SizedBox(width: 10), Expanded( child: Text( widget.activity.readingQuestion, style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: AppColors.ink, ), ), ), ], ), ), if (isOptionMode) ...[ for (var index = 0; index < options.length; index++) ...[ SectionCard( tint: selectedOptionIndex == index ? (_isOptionCorrect(index) ? AppColors.softGreen : AppColors.warm) : null, onTap: () { setState(() { selectedOptionIndex = index; showAnswer = false; }); }, child: Row( children: [ Icon( selectedOptionIndex == index ? (_isOptionCorrect(index) ? Icons.check_circle : Icons.cancel_outlined) : Icons.radio_button_off, color: selectedOptionIndex == index ? (_isOptionCorrect(index) ? AppColors.green : AppColors.warmInk) : AppColors.muted, ), const SizedBox(width: 12), Expanded( child: Text( options[index], style: TextStyle( fontSize: 15, fontWeight: selectedOptionIndex == index ? FontWeight.w600 : FontWeight.normal, color: selectedOptionIndex == index ? (_isOptionCorrect(index) ? AppColors.green : AppColors.warmInk) : AppColors.ink, ), ), ), ], ), ), ], if (hasSelected && answeredCorrectly) Container( padding: const EdgeInsets.symmetric( horizontal: 14, vertical: 10, ), decoration: BoxDecoration( color: AppColors.softGreen, borderRadius: BorderRadius.circular(10), border: Border.all( color: AppColors.green.withValues(alpha: 0.3), ), ), child: Row( children: [ Icon(Icons.check_circle, color: AppColors.green, size: 20), SizedBox(width: 8), Expanded( child: Text( '回答正确!点击下方按钮继续', style: TextStyle( color: AppColors.green, fontWeight: FontWeight.w600, fontSize: 14, ), ), ), ], ), ) else if (hasSelected && !answeredCorrectly) Container( padding: const EdgeInsets.symmetric( horizontal: 14, vertical: 10, ), decoration: BoxDecoration( color: AppColors.warm, borderRadius: BorderRadius.circular(10), border: Border.all( color: AppColors.warmInk.withValues(alpha: 0.2), ), ), child: Row( children: [ Icon( Icons.help_outline, color: AppColors.warmInk, size: 20, ), SizedBox(width: 8), Expanded( child: Text( '不对哦,再仔细观察对话中的关键句子~', style: TextStyle( color: AppColors.warmInk, fontSize: 13, ), ), ), ], ), ), ] else ...[ TextField( controller: controller, onChanged: (_) => setState(() {}), decoration: InputDecoration( hintText: '用英文输入答案', filled: true, fillColor: AppColors.surface, border: OutlineInputBorder(), ), ), ], if (showAnswer) SectionCard( tint: AppColors.warm, child: Text( '答案:${widget.activity.readingAnswer}', style: TextStyle(color: AppColors.warmInk), ), ), if (!showAnswer && ((isOptionMode && hasSelected && !answeredCorrectly) || (!isOptionMode && controller.text.isNotEmpty && !answeredCorrectly))) TextButton( onPressed: () => setState(() => showAnswer = true), child: const Text('查看答案后继续学习'), ), PrimaryButton( label: showAnswer || answeredCorrectly ? '继续写一写' : (isOptionMode ? '请选择答案' : '检查并继续'), onPressed: showAnswer || answeredCorrectly ? widget.onContinue : null, ), ], ), ); } }