Files
ai_english/client/lib/features/speaking/screens/scenario_practice_screen.dart
2025-11-17 14:09:17 +08:00

470 lines
17 KiB
Dart

import 'package:flutter/material.dart';
import '../models/conversation_scenario.dart';
/// 场景练习页面
class ScenarioPracticeScreen extends StatefulWidget {
final ConversationScenario scenario;
const ScenarioPracticeScreen({
Key? key,
required this.scenario,
}) : super(key: key);
@override
State<ScenarioPracticeScreen> createState() => _ScenarioPracticeScreenState();
}
class _ScenarioPracticeScreenState extends State<ScenarioPracticeScreen> {
int _currentStepIndex = 0;
List<String> _userResponses = [];
bool _isCompleted = false;
@override
void initState() {
super.initState();
_userResponses = List.filled(widget.scenario.steps.length, '');
}
ScenarioStep get _currentStep => widget.scenario.steps[_currentStepIndex];
void _selectOption(String option) {
setState(() {
_userResponses[_currentStepIndex] = option;
});
}
void _nextStep() {
if (_currentStepIndex < widget.scenario.steps.length - 1) {
setState(() {
_currentStepIndex++;
});
} else {
setState(() {
_isCompleted = true;
});
_showCompletionDialog();
}
}
void _previousStep() {
if (_currentStepIndex > 0) {
setState(() {
_currentStepIndex--;
});
}
}
void _showCompletionDialog() {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: const Text('🎉 恭喜完成!'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('您已成功完成「${widget.scenario.title}」场景练习!'),
const SizedBox(height: 16),
const Text('继续练习可以提高您的英语口语水平。'),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(); // 关闭对话框
Navigator.of(context).pop(); // 返回主页
},
child: const Text('返回主页'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(); // 关闭对话框
_restartScenario();
},
child: const Text('重新练习'),
),
],
),
);
}
void _restartScenario() {
setState(() {
_currentStepIndex = 0;
_userResponses = List.filled(widget.scenario.steps.length, '');
_isCompleted = false;
});
}
void _showScenarioInfo() {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => DraggableScrollableSheet(
initialChildSize: 0.7,
maxChildSize: 0.9,
minChildSize: 0.5,
builder: (context, scrollController) => Container(
padding: const EdgeInsets.all(20),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
child: SingleChildScrollView(
controller: scrollController,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
widget.scenario.type.icon,
style: const TextStyle(fontSize: 32),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.scenario.title,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Text(
widget.scenario.subtitle,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
],
),
),
],
),
const SizedBox(height: 20),
Text(
widget.scenario.description,
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 20),
const Text(
'学习目标',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
...widget.scenario.objectives.map(
(objective) => Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Row(
children: [
const Icon(Icons.check_circle,
color: Colors.green, size: 16),
const SizedBox(width: 8),
Expanded(child: Text(objective)),
],
),
),
),
const SizedBox(height: 20),
const Text(
'关键短语',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: widget.scenario.keyPhrases.map(
(phrase) => Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.blue.withOpacity(0.3)),
),
child: Text(
phrase,
style: const TextStyle(
fontSize: 12,
color: Colors.blue,
),
),
),
).toList(),
),
],
),
),
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.scenario.title,
style: const TextStyle(fontSize: 16),
),
Text(
'步骤 ${_currentStepIndex + 1}/${widget.scenario.steps.length}',
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
],
),
actions: [
IconButton(
onPressed: _showScenarioInfo,
icon: const Icon(Icons.info_outline),
),
],
),
body: Column(
children: [
// 进度条
LinearProgressIndicator(
value: (_currentStepIndex + 1) / widget.scenario.steps.length,
backgroundColor: Colors.grey[300],
valueColor: const AlwaysStoppedAnimation<Color>(Colors.blue),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 步骤标题
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_currentStep.title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
const SizedBox(height: 4),
Text(
_currentStep.description,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
],
),
),
const SizedBox(height: 20),
// 对话内容
Expanded(
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: _currentStep.role == 'npc'
? Colors.grey[100]
: Colors.blue[50],
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: _currentStep.role == 'npc'
? Colors.grey.withOpacity(0.3)
: Colors.blue.withOpacity(0.3),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
CircleAvatar(
radius: 16,
backgroundColor: _currentStep.role == 'npc'
? Colors.grey
: Colors.blue,
child: Text(
_currentStep.role == 'npc' ? 'NPC' : 'YOU',
style: const TextStyle(
fontSize: 10,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(width: 12),
Text(
_currentStep.role == 'npc' ? '对方说:' : '您说:',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 12),
Text(
_currentStep.content,
style: const TextStyle(fontSize: 16),
),
],
),
),
),
const SizedBox(height: 20),
// 选项
if (_currentStep.options.isNotEmpty) ...[
const Text(
'请选择您的回应:',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
...(_currentStep.options.asMap().entries.map(
(entry) {
final index = entry.key;
final option = entry.value;
final isSelected = _userResponses[_currentStepIndex] == option;
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: GestureDetector(
onTap: () => _selectOption(option),
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isSelected
? Colors.blue.withOpacity(0.1)
: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isSelected
? Colors.blue
: Colors.grey.withOpacity(0.3),
width: isSelected ? 2 : 1,
),
),
child: Row(
children: [
Container(
width: 24,
height: 24,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isSelected
? Colors.blue
: Colors.grey[300],
),
child: Center(
child: Text(
String.fromCharCode(65 + index), // A, B, C
style: TextStyle(
color: isSelected
? Colors.white
: Colors.grey[600],
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
option,
style: TextStyle(
fontSize: 14,
color: isSelected
? Colors.blue
: Colors.black,
),
),
),
],
),
),
),
);
},
)),
],
],
),
),
),
// 底部按钮
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, -2),
),
],
),
child: Row(
children: [
if (_currentStepIndex > 0)
Expanded(
child: OutlinedButton(
onPressed: _previousStep,
child: const Text('上一步'),
),
),
if (_currentStepIndex > 0) const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: _userResponses[_currentStepIndex].isNotEmpty ||
_currentStep.options.isEmpty
? _nextStep
: null,
child: Text(
_currentStepIndex == widget.scenario.steps.length - 1
? '完成练习'
: '下一步',
),
),
),
],
),
),
],
),
);
}
}