import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../constants/app_constants.dart'; import '../../../controllers/shared_preferences_storage_controller.dart'; import '../../../services/get/theme_controller.dart'; import '../../../models/colors/theme_colors.dart'; import '../guide/tongji.dart'; import 'level-jilu.dart'; import 'flow-anim.dart'; import 'distinguish.dart'; import 'poetry-page.dart'; import '../settings/offline-data.dart'; /// 时间: 2026-03-28 /// 功能: 诗词答题页面 /// 介绍: 基于 API 接口实现的诗词答题系统,支持获取题目、提交答案、获取提示 /// 最新变化: 添加自动加载下一题开关,隐藏提示标签,使用独立逻辑管理器 class PoetryLevelPage extends StatefulWidget { final bool showBackButton; final bool showAppBar; const PoetryLevelPage({ super.key, this.showBackButton = true, this.showAppBar = true, }); @override State createState() => _PoetryLevelPageState(); } class _PoetryLevelPageState extends State with TickerProviderStateMixin { final PoetryLevelManager _manager = PoetryLevelManager(); final ThemeController _themeController = Get.find(); bool _isLoading = true; bool _isSubmitting = false; Map? _currentQuestion; String? _errorMessage; int _score = 0; bool _autoLoadNext = true; int? _selectedAnswer; String? _feedbackMessage; bool _showFeedback = false; bool _isAnswerCorrect = false; // 动画控制器 late AnimationController _successAnimationController; late AnimationController _shakeAnimationController; late Animation _scaleAnimation; late Animation _shakeAnimation; // 标签显示状态 bool _showTags = false; // 计时器 Timer? _tagTimer; // 答题记录统计 int _totalQuestions = 0; int _correctAnswers = 0; int _wrongAnswers = 0; int _totalTime = 0; int _hintCount = 0; int _skipCount = 0; DateTime? _questionStartTime; @override void initState() { super.initState(); _initializeAndLoadQuestion(); _successAnimationController = AnimationController( duration: const Duration(milliseconds: 600), vsync: this, ); _scaleAnimation = Tween(begin: 0.8, end: 1.2).animate( CurvedAnimation( parent: _successAnimationController, curve: Curves.elasticOut, ), ); _shakeAnimationController = AnimationController( duration: const Duration(milliseconds: 500), vsync: this, ); _shakeAnimation = Tween(begin: -5, end: 5).animate( CurvedAnimation( parent: _shakeAnimationController, curve: Curves.easeInOut, ), ); // 10秒后显示标签 Future.delayed(const Duration(seconds: 10), () { if (mounted) { setState(() { _showTags = true; }); } }); } @override void dispose() { _successAnimationController.dispose(); _shakeAnimationController.dispose(); _tagTimer?.cancel(); super.dispose(); } /// 初始化并加载题目 Future _initializeAndLoadQuestion() async { setState(() { _isLoading = true; _errorMessage = null; }); // 初始化题目列表(调用 fetch 和 refresh) final initResult = await _manager.initializeQuestions(); if (!mounted) return; if (!initResult.success) { setState(() { _errorMessage = initResult.message ?? '初始化题目失败,请重试'; _isLoading = false; }); // 如果需要下载,显示下载提示 if (initResult.needDownload) { _showDownloadPrompt(); } return; } // 显示加载模式提示 if (initResult.isOffline) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(initResult.message ?? '已加载离线缓存'), backgroundColor: ThemeColors.getThemeColor( _themeController.themeColorIndexRx.value, ), duration: const Duration(seconds: 2), ), ); } // 加载第一题 await _loadQuestion(); } /// 显示下载提示 void _showDownloadPrompt() { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('提示'), content: const Text('当前无网络连接且无离线缓存数据,请先下载数据或检查网络设置。'), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); }, child: const Text('取消'), ), TextButton( onPressed: () { Navigator.of(context).pop(); // 跳转到离线数据下载页面 Navigator.push( context, MaterialPageRoute(builder: (_) => const OfflineDataPage()), ); }, child: const Text('去下载'), ), ], ); }, ); } /// 加载题目 Future _loadQuestion() async { // 开始计时 _questionStartTime = DateTime.now(); final result = await _manager.loadQuestion(); if (!mounted) return; setState(() { if (result.success) { _currentQuestion = result.data; _isLoading = false; _errorMessage = null; // 重置标签显示状态 _showTags = false; // 取消之前的计时器 _tagTimer?.cancel(); // 10秒后显示标签 _tagTimer = Timer(const Duration(seconds: 10), () { if (mounted) { setState(() { _showTags = true; }); } }); } else { _errorMessage = result.message; _isLoading = false; } _selectedAnswer = null; _feedbackMessage = null; _showFeedback = false; _isAnswerCorrect = false; }); } /// 提交答案 Future _submitAnswer(int answer) async { if (_isSubmitting || _currentQuestion == null) return; setState(() { _isSubmitting = true; _selectedAnswer = answer; _showFeedback = false; }); final result = await _manager.submitAnswer(_manager.currentId, answer); if (!mounted) return; // 记录今日答题 try { await StatisticsManager().recordTodayQuestion(); } catch (e) { // 忽略错误 } setState(() { if (result.success) { _isAnswerCorrect = result.isCorrect; if (result.message != null && result.message!.isNotEmpty) { _feedbackMessage = result.message; } else { _feedbackMessage = _isAnswerCorrect ? '🎉 回答正确!' : '😔 回答错误,再想想吧!'; } _showFeedback = true; // 计算答题时间 if (_questionStartTime != null) { final duration = DateTime.now().difference(_questionStartTime!); _totalTime += duration.inSeconds; } // 更新统计数据 _totalQuestions++; if (_isAnswerCorrect) { _correctAnswers++; _score++; _successAnimationController.forward().then((_) { _successAnimationController.reverse(); }); if (_autoLoadNext) { Future.delayed(const Duration(seconds: 2), () { if (mounted && _isAnswerCorrect) { _nextQuestion(); } }); } } else { _wrongAnswers++; _shakeAnimationController.forward().then((_) { _shakeAnimationController.reverse(); }); } // 保存答题记录 _saveAnswerRecord(isCorrect: _isAnswerCorrect); } else { _feedbackMessage = result.message; _showFeedback = true; } _isSubmitting = false; }); } /// 获取提示 Future _getHint() async { if (_isSubmitting || _currentQuestion == null) return; setState(() { _isSubmitting = true; }); final result = await _manager.getHint(_manager.currentId); if (!mounted) return; setState(() { if (result.success) { _feedbackMessage = result.message; _showFeedback = true; // 增加提示次数 _hintCount++; // 保存答题记录 _saveAnswerRecord(); } else { _feedbackMessage = result.message; _showFeedback = true; } _isSubmitting = false; }); } /// 下一题 void _nextQuestion() { // 增加跳过次数 _skipCount++; // 保存答题记录 _saveAnswerRecord(); _manager.nextQuestion(); _loadQuestion(); } /// 上一题 void _previousQuestion() { _manager.previousQuestion(); _loadQuestion(); } /// 保存答题记录到本地存储 Future _saveAnswerRecord({bool isCorrect = false}) async { try { // 保存统计数据 await SharedPreferencesStorageController.setInt( 'totalQuestions', _totalQuestions, ); await SharedPreferencesStorageController.setInt( 'correctAnswers', _correctAnswers, ); await SharedPreferencesStorageController.setInt( 'wrongAnswers', _wrongAnswers, ); await SharedPreferencesStorageController.setInt('totalTime', _totalTime); await SharedPreferencesStorageController.setInt('hintCount', _hintCount); await SharedPreferencesStorageController.setInt('skipCount', _skipCount); // 保存当前题目的详细记录 if (_currentQuestion != null) { // 构建标签列表 List tags = []; if (_currentQuestion!['type'] != null) { tags.add(_currentQuestion!['type'].toString()); } if (_currentQuestion!['grade'] != null) { tags.add(_currentQuestion!['grade'].toString()); } if (_currentQuestion!['dynasty'] != null) { tags.add(_currentQuestion!['dynasty'].toString()); } final record = { 'questionId': _manager.currentId, 'question': _currentQuestion!['question'] ?? '未知题目', 'author': _currentQuestion!['author'] ?? '未知作者', 'tags': tags, 'isCorrect': isCorrect, 'answerTime': DateTime.now().toIso8601String(), }; // 获取已有的记录列表 List records = await SharedPreferencesStorageController.getStringList( 'poetryAnswerRecords', defaultValue: [], ); // 添加新记录(JSON格式) records.add(jsonEncode(record)); // 保存更新后的列表 await SharedPreferencesStorageController.setStringList( 'poetryAnswerRecords', records, ); } } catch (e) { // 保存失败 } } /// 打开答题记录页面 void _openAnswerRecordPage() { Navigator.push( context, MaterialPageRoute(builder: (context) => const DistinguishPage()), ); } @override Widget build(BuildContext context) { return Obx(() { final isDark = _themeController.isDarkModeRx.value; final primaryColor = ThemeColors.getThemeColor( _themeController.themeColorIndexRx.value, ); return Scaffold( appBar: widget.showAppBar ? AppBar( title: Text( '诗词答题', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 20, color: Colors.white, ), ), backgroundColor: primaryColor, foregroundColor: Colors.white, elevation: 0, leading: widget.showBackButton ? null : const SizedBox.shrink(), flexibleSpace: Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [primaryColor, primaryColor.withAlpha(180)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), ), ), actions: [ Padding( padding: const EdgeInsets.only(right: 16), child: Container( decoration: BoxDecoration( color: Colors.white.withAlpha(30), borderRadius: BorderRadius.circular(8), ), child: ElevatedButton( onPressed: _openAnswerRecordPage, style: ElevatedButton.styleFrom( backgroundColor: Colors.transparent, shadowColor: Colors.transparent, padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), ), child: const Text( '记录', style: TextStyle( color: Colors.white, fontSize: 14, fontWeight: FontWeight.w600, ), ), ), ), ), ], ) : null, body: Container( color: isDark ? const Color(0xFF1A1A1A) : Colors.white, child: SafeArea( child: Padding( padding: EdgeInsets.only( left: 16.0, right: 16.0, top: 8.0, bottom: widget.showAppBar ? 16.0 : 100.0, // 关怀模式下增加底部padding ), child: Stack( children: [ Column( children: [ const SizedBox(height: 8), // 减少顶部空白 // 分数显示 AnimatedBuilder( animation: _successAnimationController, builder: (context, child) { return Transform.scale( scale: _isAnswerCorrect ? _scaleAnimation.value : 1.0, child: Container( padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 12, ), decoration: BoxDecoration( gradient: LinearGradient( colors: [ primaryColor, primaryColor.withAlpha(200), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: primaryColor.withAlpha(80), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.white.withAlpha(30), borderRadius: BorderRadius.circular( 8, ), ), child: Icon( Icons.quiz_outlined, color: Colors.white, size: 20, ), ), const SizedBox(width: 12), Text( '题目: ${_manager.currentIndex + 1}/${_manager.total}', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white, ), ), const SizedBox(width: 12), // 奖杯图标和分数 Row( children: [ Icon( Icons.emoji_events_outlined, color: Colors.amber[300], size: 24, ), const SizedBox(width: 8), Text( '$_score', style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white, ), ), ], ), ], ), Row( children: [ // 开关按钮 Tooltip( message: _autoLoadNext ? '已开启自动下一题' : '已关闭自动下一题', child: Switch( value: _autoLoadNext, onChanged: (value) { setState(() { _autoLoadNext = value; }); }, activeColor: Colors.white, activeTrackColor: Colors.white .withAlpha(128), ), ), ], ), ], ), ), ); }, ), const SizedBox(height: 10), if (_isLoading) Expanded( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator( valueColor: AlwaysStoppedAnimation( primaryColor, ), strokeWidth: 3, ), const SizedBox(height: 20), Text( '加载题目中...', style: TextStyle( fontSize: 16, color: isDark ? Colors.grey[400] : Colors.grey, ), ), ], ), ), ), if (!_isLoading && _errorMessage != null) Expanded( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: isDark ? Colors.red[900]!.withAlpha(30) : Colors.red[50], shape: BoxShape.circle, ), child: Icon( Icons.error_outline, size: 64, color: Colors.red[400], ), ), const SizedBox(height: 24), Text( _errorMessage!, textAlign: TextAlign.center, style: TextStyle( fontSize: 16, color: isDark ? Colors.red[300] : Colors.red, ), ), const SizedBox(height: 32), Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [ primaryColor, primaryColor.withAlpha(200), ], ), borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: primaryColor.withAlpha(80), blurRadius: 8, offset: const Offset(0, 4), ), ], ), child: ElevatedButton( onPressed: () => _loadQuestion(), style: ElevatedButton.styleFrom( backgroundColor: Colors.transparent, shadowColor: Colors.transparent, padding: const EdgeInsets.symmetric( horizontal: 32, vertical: 16, ), ), child: const Text( '重新加载', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white, ), ), ), ), ], ), ), ), if (!_isLoading && _currentQuestion != null) Expanded( child: Column( children: [ Expanded( child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ AnimatedBuilder( animation: _shakeAnimationController, builder: (context, child) { return Transform.translate( offset: Offset( _isAnswerCorrect ? 0 : _shakeAnimation.value, 0, ), child: Stack( children: [ Positioned.fill( child: FlowingBorderContainer( child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular( 16, ), color: isDark ? const Color( 0xFF2A2A2A, ) : Colors.white, ), ), color: primaryColor, width: 4, ), ), Container( padding: const EdgeInsets.all( 16, ), decoration: BoxDecoration( gradient: LinearGradient( colors: [ isDark ? const Color( 0xFF2A2A2A, ) : Colors.white, AppConstants .primaryColor .withAlpha(5), isDark ? const Color( 0xFF2A2A2A, ) : Colors.white, ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular( 16, ), backgroundBlendMode: BlendMode.softLight, ), child: Column( crossAxisAlignment: CrossAxisAlignment .start, children: [ // 装饰元素 Row( children: [ Container( width: 4, height: 20, decoration: BoxDecoration( color: AppConstants .primaryColor, borderRadius: BorderRadius.circular( 2, ), ), ), const SizedBox( width: 12, ), Text( '诗词挑战', style: TextStyle( fontSize: 14, fontWeight: FontWeight .w600, color: AppConstants .primaryColor, ), ), ], ), const SizedBox( height: 12, ), Text( _currentQuestion!['question'] ?? '题目加载失败', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, height: 1.5, color: isDark ? Colors.white : Colors.black87, ), ), // 标签信息 if (_showTags) AnimatedOpacity( duration: const Duration( milliseconds: 500, ), opacity: 1, child: Container( margin: const EdgeInsets.only( top: 12, ), padding: const EdgeInsets.all( 12, ), decoration: BoxDecoration( color: AppConstants .primaryColor .withAlpha( 10, ), borderRadius: BorderRadius.circular( 8, ), ), child: Row( mainAxisAlignment: MainAxisAlignment .spaceBetween, children: [ PoetryTag( label: '作者', value: _currentQuestion!['author'] ?? '', ), PoetryTag( label: '年代', value: _currentQuestion!['dynasty'] ?? '', ), PoetryTag( label: '类型', value: _currentQuestion!['type'] ?? '', ), PoetryTag( label: '阶段', value: _currentQuestion!['grade'] ?? '', ), ], ), ), ), ], ), ), ], ), ); }, ), const SizedBox(height: 10), // 选项 PoetryOptionsLayout( options: _currentQuestion!['options'] as List, selectedAnswer: _selectedAnswer, showFeedback: _showFeedback, isAnswerCorrect: _isAnswerCorrect, isSubmitting: _isSubmitting, onTap: (optionNum) { if (optionNum == -1) { // 重置状态 setState(() { _showFeedback = false; _selectedAnswer = null; _feedbackMessage = null; }); } else { _submitAnswer(optionNum); } }, ), ], ), ), ), // 固定位置的操作按钮卡片(在底部固定,不随内容滚动) Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withAlpha(10), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: Column( children: [ // 操作按钮 - 改为一行显示 Row( children: [ // 上一题按钮 Expanded( child: Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [ Colors.white, Colors.grey[50]!, ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(12), border: Border.all( color: primaryColor.withAlpha( 50, ), width: 1, ), ), child: OutlinedButton( onPressed: _previousQuestion, style: OutlinedButton.styleFrom( side: BorderSide.none, padding: const EdgeInsets.symmetric( vertical: 14, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.arrow_back, color: AppConstants .primaryColor, size: 20, ), const SizedBox(width: 8), const Text( '上一题', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: Colors.black87, ), ), ], ), ), ), ), const SizedBox(width: 12), // 提示按钮 Expanded( child: Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [ Colors.white, Colors.grey[50]!, ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(12), border: Border.all( color: primaryColor, width: 2, ), boxShadow: [ BoxShadow( color: primaryColor.withAlpha( 30, ), blurRadius: 8, offset: const Offset(0, 4), ), ], ), child: ElevatedButton( onPressed: _getHint, style: ElevatedButton.styleFrom( backgroundColor: Colors.transparent, shadowColor: Colors.transparent, padding: const EdgeInsets.symmetric( vertical: 14, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.lightbulb_outline, color: AppConstants .primaryColor, size: 20, ), const SizedBox(width: 8), const Text( '提示', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ], ), ), ), ), const SizedBox(width: 12), // 下一题按钮 Expanded( child: Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [ primaryColor, primaryColor.withAlpha(200), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: AppConstants .primaryColor .withAlpha(80), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: ElevatedButton( onPressed: _nextQuestion, style: ElevatedButton.styleFrom( backgroundColor: Colors.transparent, shadowColor: Colors.transparent, padding: const EdgeInsets.symmetric( vertical: 14, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( '下一题', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.white, ), ), const SizedBox(width: 8), Icon( Icons.arrow_forward, color: Colors.white, size: 20, ), ], ), ), ), ), ], ), ], ), ), ], ), ), ], ), // 反馈信息气泡(不占用布局) if (_showFeedback && _feedbackMessage != null) Positioned( top: 0, left: 16, right: 16, child: AnimatedContainer( duration: const Duration(milliseconds: 500), curve: Curves.easeOut, transform: Matrix4.translationValues(0, 0, 0), padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 12, ), decoration: BoxDecoration( gradient: LinearGradient( colors: _isAnswerCorrect ? [Colors.green[400]!, Colors.green[300]!] : [Colors.orange[400]!, Colors.orange[300]!], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: (_isAnswerCorrect ? Colors.green : Colors.orange) .withAlpha(80), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: Row( children: [ Icon( _isAnswerCorrect ? Icons.celebration : Icons.lightbulb_outline, color: Colors.white, size: 24, ), const SizedBox(width: 12), Expanded( child: Text( _feedbackMessage!, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.white, ), ), ), ], ), ), ), ], ), ), ), ), ); }); } }