diff --git a/lib/views/profile/level/distinguish.dart b/lib/views/profile/level/distinguish.dart index 70c09be..8edabb4 100644 --- a/lib/views/profile/level/distinguish.dart +++ b/lib/views/profile/level/distinguish.dart @@ -11,6 +11,7 @@ import 'package:get/get.dart'; import 'package:intl/intl.dart'; import '../../../models/colors/app_colors.dart'; +import '../../../models/colors/theme_colors.dart'; import '../../../controllers/shared_preferences_storage_controller.dart'; import '../../../controllers/history_controller.dart'; import '../../../services/network_listener_service.dart'; @@ -262,16 +263,8 @@ class _DistinguishPageState extends State { child: Column( children: [ _buildStatRow('已答题', '$_totalQuestions 题'), - _buildStatRow( - '正确', - '$_correctAnswers 题', - isGreen: true, - ), - _buildStatRow( - '错误', - '$_wrongAnswers 题', - isRed: true, - ), + _buildStatRow('正确', '$_correctAnswers 题', isGreen: true), + _buildStatRow('错误', '$_wrongAnswers 题', isRed: true), _buildStatRow( '正确率', '${_correctRate.toStringAsFixed(1)}%', @@ -288,15 +281,8 @@ class _DistinguishPageState extends State { ), _buildStatRow('提示次数', '$_hintCount 次'), _buildStatRow('跳过次数', '$_skipCount 次'), - Divider( - height: 24, - color: AppColors.divider, - ), - _buildStatRow( - '诗词水平', - _poetryLevel, - isHighlight: true, - ), + Divider(height: 24, color: AppColors.divider), + _buildStatRow('诗词水平', _poetryLevel, isHighlight: true), ], ), ), @@ -378,10 +364,7 @@ class _DistinguishPageState extends State { children: [ Text( label, - style: TextStyle( - fontSize: 15, - color: AppColors.secondaryText, - ), + style: TextStyle(fontSize: 15, color: AppColors.secondaryText), ), Text( value, @@ -453,7 +436,10 @@ $_poetryLevel builder: (context) => AlertDialog( backgroundColor: AppColors.surface, title: Text('确认清空', style: TextStyle(color: AppColors.primaryText)), - content: Text('确定要清空所有答题记录吗?此操作不可恢复。', style: TextStyle(color: AppColors.secondaryText)), + content: Text( + '确定要清空所有答题记录吗?此操作不可恢复。', + style: TextStyle(color: AppColors.secondaryText), + ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), @@ -500,6 +486,9 @@ $_poetryLevel Widget build(BuildContext context) { return Obx(() { final isDark = _themeController.isDarkMode; + final primaryColor = ThemeColors.getThemeColor( + _themeController.themeColorIndexRx.value, + ); return Scaffold( appBar: AppBar( title: Text( @@ -510,16 +499,13 @@ $_poetryLevel color: Colors.white, ), ), - backgroundColor: AppColors.primary, + backgroundColor: primaryColor, foregroundColor: Colors.white, elevation: 0, flexibleSpace: Container( decoration: BoxDecoration( gradient: LinearGradient( - colors: [ - AppColors.primary, - AppColors.primary.withAlpha(180), - ], + colors: [primaryColor, primaryColor.withAlpha(180)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), @@ -540,7 +526,7 @@ $_poetryLevel ], ), body: Container( - color: AppColors.background, + color: isDark ? const Color(0xFF1A1A1A) : AppColors.background, child: SafeArea( child: _isLoading ? const Center(child: CircularProgressIndicator()) @@ -577,10 +563,7 @@ $_poetryLevel const SizedBox(height: 8), Text( '快去答题吧!', - style: TextStyle( - fontSize: 14, - color: AppColors.tertiaryText, - ), + style: TextStyle(fontSize: 14, color: AppColors.tertiaryText), ), ], ), @@ -591,195 +574,210 @@ $_poetryLevel Widget _buildRecordList() { return Obx(() { final isDark = _themeController.isDarkMode; + final primaryColor = ThemeColors.getThemeColor( + _themeController.themeColorIndexRx.value, + ); return ListView.builder( padding: const EdgeInsets.all(16), itemCount: _answerRecords.length, itemBuilder: (context, index) { final record = _answerRecords[index]; - return _buildRecordCard(record, index); + return _buildRecordCard(record, index, primaryColor, isDark); }, ); }); } - Widget _buildRecordCard(Map record, int index) { + Widget _buildRecordCard( + Map record, + int index, + Color primaryColor, + bool isDark, + ) { final question = record['question'] ?? '未知题目'; final author = record['author'] ?? '未知作者'; final tags = (record['tags'] as List?)?.cast() ?? []; final isCorrect = record['isCorrect'] ?? false; final answerTime = _formatTime(record['answerTime']); - return Obx(() { - final isDark = _themeController.isDarkMode; - return Container( - margin: const EdgeInsets.only(bottom: 12), - decoration: BoxDecoration( - color: AppColors.surface, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.black.withAlpha(isDark ? 30 : 10), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: 28, - height: 28, - decoration: BoxDecoration( - color: AppColors.primary.withAlpha(20), - borderRadius: BorderRadius.circular(8), - ), - child: Center( - child: Text( - '${index + 1}', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: AppColors.primary, - ), + return Container( + margin: const EdgeInsets.only(bottom: 12), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF2A2A2A) : AppColors.surface, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withAlpha(isDark ? 30 : 10), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 28, + height: 28, + decoration: BoxDecoration( + color: primaryColor.withAlpha(20), + borderRadius: BorderRadius.circular(8), + ), + child: Center( + child: Text( + '${index + 1}', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: primaryColor, ), ), ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - question, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: AppColors.primaryText, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 4), - Text( - '—— $author', - style: TextStyle( - fontSize: 12, - color: AppColors.secondaryText, - fontStyle: FontStyle.italic, - ), - ), - ], - ), - ), - const SizedBox(width: 8), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 4, - ), - decoration: BoxDecoration( - color: isCorrect - ? AppColors.iosGreen.withAlpha(20) - : AppColors.iosRed.withAlpha(20), - borderRadius: BorderRadius.circular(12), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - isCorrect ? Icons.check_circle : Icons.cancel, - size: 14, - color: isCorrect ? AppColors.iosGreen : AppColors.iosRed, - ), - const SizedBox(width: 4), - Text( - isCorrect ? '答对' : '答错', - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: isCorrect ? AppColors.iosGreen : AppColors.iosRed, - ), - ), - ], - ), - ), - ], - ), - const SizedBox(height: 12), - Row( - children: [ - Expanded( - child: tags.isNotEmpty - ? Wrap( - spacing: 6, - runSpacing: 6, - children: tags.map((tag) { - return Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 3, - ), - decoration: BoxDecoration( - color: AppColors.primary.withAlpha(15), - borderRadius: BorderRadius.circular(6), - border: Border.all( - color: AppColors.primary.withAlpha( - 50, - ), - width: 0.5, - ), - ), - child: Text( - tag, - style: TextStyle( - fontSize: 11, - color: AppColors.primary, - fontWeight: FontWeight.w500, - ), - ), - ); - }).toList(), - ) - : Text( - '暂无标签', - style: TextStyle( - fontSize: 12, - color: AppColors.tertiaryText, - fontStyle: FontStyle.italic, - ), - ), - ), - Row( - mainAxisSize: MainAxisSize.min, + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Icon( - Icons.access_time, - size: 12, - color: AppColors.tertiaryText, - ), - const SizedBox(width: 4), Text( - answerTime, + question, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: isDark + ? Colors.grey[300] + : AppColors.primaryText, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + Text( + '—— $author', style: TextStyle( fontSize: 12, - color: AppColors.secondaryText, + color: isDark + ? Colors.grey[500] + : AppColors.secondaryText, + fontStyle: FontStyle.italic, ), ), ], ), - ], - ), - ], - ), + ), + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 4, + ), + decoration: BoxDecoration( + color: isCorrect + ? AppColors.iosGreen.withAlpha(20) + : AppColors.iosRed.withAlpha(20), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + isCorrect ? Icons.check_circle : Icons.cancel, + size: 14, + color: isCorrect + ? AppColors.iosGreen + : AppColors.iosRed, + ), + const SizedBox(width: 4), + Text( + isCorrect ? '答对' : '答错', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: isCorrect + ? AppColors.iosGreen + : AppColors.iosRed, + ), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: tags.isNotEmpty + ? Wrap( + spacing: 6, + runSpacing: 6, + children: tags.map((tag) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 3, + ), + decoration: BoxDecoration( + color: primaryColor.withAlpha(15), + borderRadius: BorderRadius.circular(6), + border: Border.all( + color: primaryColor.withAlpha(50), + width: 0.5, + ), + ), + child: Text( + tag, + style: TextStyle( + fontSize: 11, + color: primaryColor, + fontWeight: FontWeight.w500, + ), + ), + ); + }).toList(), + ) + : Text( + '暂无标签', + style: TextStyle( + fontSize: 12, + color: isDark + ? Colors.grey[500] + : AppColors.tertiaryText, + fontStyle: FontStyle.italic, + ), + ), + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.access_time, + size: 12, + color: isDark ? Colors.grey[500] : AppColors.tertiaryText, + ), + const SizedBox(width: 4), + Text( + answerTime, + style: TextStyle( + fontSize: 12, + color: isDark + ? Colors.grey[400] + : AppColors.secondaryText, + ), + ), + ], + ), + ], + ), + ], ), - ); - }); + ), + ); } // 写入统计数据到笔记 diff --git a/lib/views/profile/level/flow-anim.dart b/lib/views/profile/level/flow-anim.dart index 56398f9..9f51051 100644 --- a/lib/views/profile/level/flow-anim.dart +++ b/lib/views/profile/level/flow-anim.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../services/get/theme_controller.dart'; +import '../../../models/colors/theme_colors.dart'; /// 流动边框装饰器 class FlowingBorderDecoration extends Decoration { @@ -108,7 +109,10 @@ class _FlowingBorderContainerState extends State @override Widget build(BuildContext context) { return Obx(() { - final color = widget.color ?? _themeController.currentThemeColor; + final color = + widget.color ?? + ThemeColors.getThemeColor(_themeController.themeColorIndexRx.value); + return AnimatedBuilder( animation: _animation, builder: (context, child) { diff --git a/lib/views/profile/level/poetry-page.dart b/lib/views/profile/level/poetry-page.dart new file mode 100644 index 0000000..81a836c --- /dev/null +++ b/lib/views/profile/level/poetry-page.dart @@ -0,0 +1,327 @@ +/// 诗词答题页面组件 +library; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../../../services/get/theme_controller.dart'; +import '../../../models/colors/theme_colors.dart'; + +/// 单个选项组件 +class PoetryOptionItem extends StatelessWidget { + final dynamic option; + final bool isSelected; + final bool isCorrect; + final bool isWrong; + final bool isSubmitting; + final bool showFeedback; + final bool isAnswerCorrect; + final Function(int) onTap; + + const PoetryOptionItem({ + super.key, + required this.option, + required this.isSelected, + required this.isCorrect, + required this.isWrong, + required this.isSubmitting, + required this.showFeedback, + required this.isAnswerCorrect, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + final themeController = Get.find(); + + return Obx(() { + final isDark = themeController.isDarkModeRx.value; + final primaryColor = ThemeColors.getThemeColor( + themeController.themeColorIndexRx.value, + ); + + final optionNum = option['index'] ?? option['num'] ?? 0; + + return AnimatedContainer( + duration: const Duration(milliseconds: 300), + child: Container( + decoration: BoxDecoration( + gradient: isSelected + ? LinearGradient( + colors: isCorrect + ? [Colors.green[400]!, Colors.green[300]!] + : isWrong + ? [Colors.red[400]!, Colors.red[300]!] + : [primaryColor, primaryColor.withAlpha(200)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ) + : null, + color: isSelected + ? null + : isDark + ? const Color(0xFF2A2A2A) + : Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: isSelected + ? Colors.transparent + : primaryColor.withAlpha(50), + width: 2, + ), + boxShadow: isSelected + ? [ + BoxShadow( + color: + (isCorrect + ? Colors.green + : isWrong + ? Colors.red + : primaryColor) + .withAlpha(80), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ] + : [ + BoxShadow( + color: isDark + ? Colors.white.withAlpha(5) + : Colors.black.withAlpha(5), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: isSubmitting || (showFeedback && isAnswerCorrect) + ? null + : () { + if (showFeedback) { + onTap(-1); // -1 表示重置状态 + } else { + onTap(optionNum); + } + }, + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 300), + width: 32, + height: 32, + decoration: BoxDecoration( + gradient: isSelected + ? LinearGradient( + colors: [ + Colors.white, + Colors.white.withAlpha(230), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ) + : null, + color: isSelected ? null : primaryColor.withAlpha(20), + shape: BoxShape.circle, + boxShadow: isSelected + ? [ + BoxShadow( + color: Colors.black.withAlpha(20), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ] + : null, + ), + child: Center( + child: Text( + '$optionNum', + style: TextStyle( + color: isSelected + ? (isCorrect + ? Colors.green + : isWrong + ? Colors.red + : primaryColor) + : primaryColor, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Text( + option['content'] ?? option['text'] ?? '', + style: TextStyle( + fontSize: 17, + fontWeight: FontWeight.w500, + color: isSelected + ? Colors.white + : isDark + ? Colors.grey[300] + : Colors.black87, + ), + ), + ), + if (isSelected) + Icon( + isCorrect + ? Icons.check_circle + : isWrong + ? Icons.cancel + : Icons.radio_button_checked, + color: Colors.white, + size: 28, + ), + ], + ), + ), + ), + ), + ), + ); + }); + } +} + +/// 选项布局组件 +class PoetryOptionsLayout extends StatelessWidget { + final List options; + final int? selectedAnswer; + final bool showFeedback; + final bool isAnswerCorrect; + final bool isSubmitting; + final Function(int) onTap; + + const PoetryOptionsLayout({ + super.key, + required this.options, + required this.selectedAnswer, + required this.showFeedback, + required this.isAnswerCorrect, + required this.isSubmitting, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + if (options.isEmpty) { + return const SizedBox(); + } + + // 检查是否所有选项都少于等于4个字 + bool allShortOptions = options.every((option) { + final text = option['content'] ?? ''; + return text.length <= 4; + }); + + if (allShortOptions && options.length >= 4) { + // 2*2布局 + return Column( + children: [ + Row( + children: [ + Expanded(child: _buildOptionItem(options[0])), + const SizedBox(width: 12), + Expanded(child: _buildOptionItem(options[1])), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + Expanded(child: _buildOptionItem(options[2])), + const SizedBox(width: 12), + Expanded(child: _buildOptionItem(options[3])), + ], + ), + ], + ); + } else { + // 1*4布局 + final List optionWidgets = []; + for (int i = 0; i < options.length; i++) { + optionWidgets.add(_buildOptionItem(options[i])); + if (i < options.length - 1) { + optionWidgets.add(const SizedBox(height: 12)); + } + } + return Column(children: optionWidgets); + } + } + + Widget _buildOptionItem(dynamic option) { + final optionNum = option['index'] ?? option['num'] ?? 0; + final isSelected = selectedAnswer == optionNum; + final isCorrect = + showFeedback && isAnswerCorrect && selectedAnswer == optionNum; + final isWrong = + showFeedback && !isAnswerCorrect && selectedAnswer == optionNum; + + return PoetryOptionItem( + option: option, + isSelected: isSelected, + isCorrect: isCorrect, + isWrong: isWrong, + isSubmitting: isSubmitting, + showFeedback: showFeedback, + isAnswerCorrect: isAnswerCorrect, + onTap: onTap, + ); + } +} + +/// 标签组件 +class PoetryTag extends StatelessWidget { + final String label; + final String value; + + const PoetryTag({super.key, required this.label, required this.value}); + + @override + Widget build(BuildContext context) { + if (value.isEmpty) return const SizedBox(); + + final themeController = Get.find(); + + return Obx(() { + final isDark = themeController.isDarkModeRx.value; + final primaryColor = ThemeColors.getThemeColor( + themeController.themeColorIndexRx.value, + ); + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: primaryColor.withAlpha(20), + borderRadius: BorderRadius.circular(4), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + label, + style: TextStyle( + fontSize: 10, + color: primaryColor, + fontWeight: FontWeight.w600, + ), + ), + Text( + value, + style: TextStyle( + fontSize: 12, + color: isDark ? Colors.grey[300] : Colors.black87, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ); + }); + } +} diff --git a/lib/views/profile/level/poetry.dart b/lib/views/profile/level/poetry.dart index 74ff29e..2a00a85 100644 --- a/lib/views/profile/level/poetry.dart +++ b/lib/views/profile/level/poetry.dart @@ -1,25 +1,34 @@ -/// 时间: 2026-03-28 -/// 功能: 诗词答题页面 -/// 介绍: 基于 API 接口实现的诗词答题系统,支持获取题目、提交答案、获取提示 -/// 最新变化: 添加自动加载下一题开关,隐藏提示标签,使用独立逻辑管理器,支持主题色设置 - import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import '../../../models/colors/app_colors.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 { - const PoetryLevelPage({super.key}); + final bool showBackButton; + final bool showAppBar; + + const PoetryLevelPage({ + super.key, + this.showBackButton = true, + this.showAppBar = true, + }); @override State createState() => _PoetryLevelPageState(); @@ -137,7 +146,9 @@ class _PoetryLevelPageState extends State ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(initResult.message ?? '已加载离线缓存'), - backgroundColor: AppColors.primary, + backgroundColor: ThemeColors.getThemeColor( + _themeController.themeColorIndexRx.value, + ), duration: const Duration(seconds: 2), ), ); @@ -153,15 +164,14 @@ class _PoetryLevelPageState extends State context: context, builder: (BuildContext context) { return AlertDialog( - backgroundColor: AppColors.surface, - title: Text('提示', style: TextStyle(color: AppColors.primaryText)), - content: Text('当前无网络连接且无离线缓存数据,请先下载数据或检查网络设置。', style: TextStyle(color: AppColors.secondaryText)), + title: const Text('提示'), + content: const Text('当前无网络连接且无离线缓存数据,请先下载数据或检查网络设置。'), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); }, - child: Text('取消', style: TextStyle(color: AppColors.primary)), + child: const Text('取消'), ), TextButton( onPressed: () { @@ -172,7 +182,7 @@ class _PoetryLevelPageState extends State MaterialPageRoute(builder: (_) => const OfflineDataPage()), ); }, - child: Text('去下载', style: TextStyle(color: AppColors.primary)), + child: const Text('去下载'), ), ], ); @@ -407,317 +417,78 @@ class _PoetryLevelPageState extends State ); } - /// 构建选项布局 - Widget _buildOptionsLayout() { - if (_currentQuestion == null) { - return const SizedBox(); - } - - final options = _currentQuestion!['options'] as List?; - if (options == null || options.isEmpty) { - return const SizedBox(); - } - - // 检查是否所有选项都少于等于4个字 - bool allShortOptions = options.every((option) { - final text = option['content'] ?? ''; - return text.length <= 4; - }); - - if (allShortOptions && options.length >= 4) { - // 2*2布局 - return Column( - children: [ - Row( - children: [ - Expanded(child: _buildOptionItem(options[0])), - const SizedBox(width: 12), - Expanded(child: _buildOptionItem(options[1])), - ], - ), - const SizedBox(height: 12), - Row( - children: [ - Expanded(child: _buildOptionItem(options[2])), - const SizedBox(width: 12), - Expanded(child: _buildOptionItem(options[3])), - ], - ), - ], - ); - } else { - // 1*4布局 - final List optionWidgets = []; - for (int i = 0; i < options.length; i++) { - optionWidgets.add(_buildOptionItem(options[i])); - if (i < options.length - 1) { - optionWidgets.add(const SizedBox(height: 12)); - } - } - return Column(children: optionWidgets); - } - } - - /// 构建标签 - Widget _buildTag(String label, String value) { - if (value.isEmpty) return const SizedBox(); - - return Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: AppColors.primary.withAlpha(20), - borderRadius: BorderRadius.circular(4), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - label, - style: TextStyle( - fontSize: 10, - color: AppColors.primary, - fontWeight: FontWeight.w600, - ), - ), - Text( - value, - style: TextStyle( - fontSize: 12, - color: AppColors.primaryText, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - ); - } - - /// 构建单个选项 - Widget _buildOptionItem(dynamic option) { - final optionNum = option['index'] ?? option['num'] ?? 0; - final isSelected = _selectedAnswer == optionNum; - final isCorrect = - _showFeedback && _isAnswerCorrect && _selectedAnswer == optionNum; - final isWrong = - _showFeedback && !_isAnswerCorrect && _selectedAnswer == optionNum; - + @override + Widget build(BuildContext context) { return Obx(() { - final isDark = _themeController.isDarkMode; - return AnimatedContainer( - duration: const Duration(milliseconds: 300), - child: Container( - decoration: BoxDecoration( - gradient: isSelected - ? LinearGradient( - colors: isCorrect - ? [AppColors.iosGreen, AppColors.iosGreen.withAlpha(200)] - : isWrong - ? [AppColors.iosRed, AppColors.iosRed.withAlpha(200)] - : [ - AppColors.primary, - AppColors.primary.withAlpha(200), - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ) - : null, - color: isSelected ? null : AppColors.surface, - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: isSelected - ? Colors.transparent - : AppColors.primary.withAlpha(50), - width: 2, - ), - boxShadow: isSelected - ? [ - BoxShadow( - color: (isCorrect - ? AppColors.iosGreen - : isWrong - ? AppColors.iosRed - : AppColors.primary) - .withAlpha(80), - blurRadius: 12, - offset: const Offset(0, 4), + 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, ), - ] - : [ - BoxShadow( - color: Colors.black.withAlpha(isDark ? 10 : 5), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: _isSubmitting || (_showFeedback && _isAnswerCorrect) - ? null - : () { - if (_showFeedback) { - // 重置状态,允许重新选择 - setState(() { - _showFeedback = false; - _selectedAnswer = null; - _feedbackMessage = null; - }); - } - _submitAnswer(optionNum); - }, - borderRadius: BorderRadius.circular(12), - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - AnimatedContainer( - duration: const Duration(milliseconds: 300), - width: 32, - height: 32, + ), + ), + actions: [ + Padding( + padding: const EdgeInsets.only(right: 16), + child: Container( decoration: BoxDecoration( - gradient: isSelected - ? LinearGradient( - colors: isCorrect - ? [Colors.white, Colors.white.withAlpha(230)] - : isWrong - ? [Colors.white, Colors.white.withAlpha(230)] - : [Colors.white, Colors.white.withAlpha(230)], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ) - : null, - color: isSelected - ? null - : AppColors.primary.withAlpha(20), - shape: BoxShape.circle, - boxShadow: isSelected - ? [ - BoxShadow( - color: Colors.black.withAlpha(20), - blurRadius: 4, - offset: const Offset(0, 2), - ), - ] - : null, + color: Colors.white.withAlpha(30), + borderRadius: BorderRadius.circular(8), ), - child: Center( - child: Text( - '$optionNum', + 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: isSelected - ? (isCorrect - ? AppColors.iosGreen - : isWrong - ? AppColors.iosRed - : AppColors.primary) - : AppColors.primary, - fontWeight: FontWeight.bold, - fontSize: 16, + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w600, ), ), ), ), - const SizedBox(width: 16), - Expanded( - child: Text( - option['content'] ?? option['text'] ?? '', - style: TextStyle( - fontSize: 17, - fontWeight: FontWeight.w500, - color: isSelected ? Colors.white : AppColors.primaryText, - ), - ), - ), - if (isSelected) - Icon( - isCorrect - ? Icons.check_circle - : isWrong - ? Icons.cancel - : Icons.radio_button_checked, - color: Colors.white, - size: 28, - ), - ], - ), - ), - ), - ), - ), - ); - }); - } - - @override - Widget build(BuildContext context) { - return Obx(() { - final isDark = _themeController.isDarkMode; - return Scaffold( - appBar: AppBar( - title: Text( - '诗词答题', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, - color: Colors.white, - ), - ), - backgroundColor: AppColors.primary, - foregroundColor: Colors.white, - elevation: 0, - flexibleSpace: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - AppColors.primary, - AppColors.primary.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: AppColors.background, + color: isDark ? const Color(0xFF1A1A1A) : Colors.white, child: SafeArea( child: Padding( - padding: const EdgeInsets.only( + padding: EdgeInsets.only( left: 16.0, right: 16.0, top: 8.0, - bottom: 16.0, + bottom: widget.showAppBar ? 16.0 : 100.0, // 关怀模式下增加底部padding ), child: Stack( children: [ @@ -740,8 +511,8 @@ class _PoetryLevelPageState extends State decoration: BoxDecoration( gradient: LinearGradient( colors: [ - AppColors.primary, - AppColors.primary.withAlpha(200), + primaryColor, + primaryColor.withAlpha(200), ], begin: Alignment.topLeft, end: Alignment.bottomRight, @@ -749,9 +520,7 @@ class _PoetryLevelPageState extends State borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( - color: AppColors.primary.withAlpha( - 80, - ), + color: primaryColor.withAlpha(80), blurRadius: 12, offset: const Offset(0, 4), ), @@ -844,7 +613,7 @@ class _PoetryLevelPageState extends State children: [ CircularProgressIndicator( valueColor: AlwaysStoppedAnimation( - AppColors.primary, + primaryColor, ), strokeWidth: 3, ), @@ -853,7 +622,9 @@ class _PoetryLevelPageState extends State '加载题目中...', style: TextStyle( fontSize: 16, - color: AppColors.tertiaryText, + color: isDark + ? Colors.grey[400] + : Colors.grey, ), ), ], @@ -871,14 +642,14 @@ class _PoetryLevelPageState extends State padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: isDark - ? AppColors.iosRed.withAlpha(30) - : AppColors.iosRed.withAlpha(10), + ? Colors.red[900]!.withAlpha(30) + : Colors.red[50], shape: BoxShape.circle, ), child: Icon( Icons.error_outline, size: 64, - color: AppColors.iosRed, + color: Colors.red[400], ), ), const SizedBox(height: 24), @@ -887,7 +658,9 @@ class _PoetryLevelPageState extends State textAlign: TextAlign.center, style: TextStyle( fontSize: 16, - color: AppColors.iosRed, + color: isDark + ? Colors.red[300] + : Colors.red, ), ), const SizedBox(height: 32), @@ -895,17 +668,14 @@ class _PoetryLevelPageState extends State decoration: BoxDecoration( gradient: LinearGradient( colors: [ - AppColors.primary, - AppColors.primary.withAlpha( - 200, - ), + primaryColor, + primaryColor.withAlpha(200), ], ), borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( - color: AppColors.primary - .withAlpha(80), + color: primaryColor.withAlpha(80), blurRadius: 8, offset: const Offset(0, 4), ), @@ -966,9 +736,15 @@ class _PoetryLevelPageState extends State BorderRadius.circular( 16, ), - color: AppColors.surface, + color: isDark + ? const Color( + 0xFF2A2A2A, + ) + : Colors.white, ), ), + color: AppConstants + .primaryColor, width: 4, ), ), @@ -979,10 +755,19 @@ class _PoetryLevelPageState extends State decoration: BoxDecoration( gradient: LinearGradient( colors: [ - AppColors.surface, - AppColors.primary + isDark + ? const Color( + 0xFF2A2A2A, + ) + : Colors.white, + AppConstants + .primaryColor .withAlpha(5), - AppColors.surface, + isDark + ? const Color( + 0xFF2A2A2A, + ) + : Colors.white, ], begin: Alignment.topLeft, end: @@ -1007,8 +792,8 @@ class _PoetryLevelPageState extends State width: 4, height: 20, decoration: BoxDecoration( - color: AppColors - .primary, + color: AppConstants + .primaryColor, borderRadius: BorderRadius.circular( 2, @@ -1025,8 +810,8 @@ class _PoetryLevelPageState extends State fontWeight: FontWeight .w600, - color: AppColors - .primary, + color: AppConstants + .primaryColor, ), ), ], @@ -1042,7 +827,9 @@ class _PoetryLevelPageState extends State fontWeight: FontWeight.bold, height: 1.5, - color: AppColors.primaryText, + color: isDark + ? Colors.white + : Colors.black87, ), ), // 标签信息 @@ -1064,8 +851,8 @@ class _PoetryLevelPageState extends State 12, ), decoration: BoxDecoration( - color: AppColors - .primary + color: AppConstants + .primaryColor .withAlpha( 10, ), @@ -1079,24 +866,28 @@ class _PoetryLevelPageState extends State MainAxisAlignment .spaceBetween, children: [ - _buildTag( - '作者', - _currentQuestion!['author'] ?? + PoetryTag( + label: '作者', + value: + _currentQuestion!['author'] ?? '', ), - _buildTag( - '年代', - _currentQuestion!['dynasty'] ?? + PoetryTag( + label: '年代', + value: + _currentQuestion!['dynasty'] ?? '', ), - _buildTag( - '类型', - _currentQuestion!['type'] ?? + PoetryTag( + label: '类型', + value: + _currentQuestion!['type'] ?? '', ), - _buildTag( - '阶段', - _currentQuestion!['grade'] ?? + PoetryTag( + label: '阶段', + value: + _currentQuestion!['grade'] ?? '', ), ], @@ -1113,7 +904,27 @@ class _PoetryLevelPageState extends State ), const SizedBox(height: 10), // 选项 - _buildOptionsLayout(), + 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); + } + }, + ), ], ), ), @@ -1123,7 +934,7 @@ class _PoetryLevelPageState extends State width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: AppColors.surface, + color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( @@ -1144,8 +955,8 @@ class _PoetryLevelPageState extends State decoration: BoxDecoration( gradient: LinearGradient( colors: [ - AppColors.surface, - AppColors.background, + Colors.white, + Colors.grey[50]!, ], begin: Alignment.topLeft, end: Alignment.bottomRight, @@ -1153,8 +964,9 @@ class _PoetryLevelPageState extends State borderRadius: BorderRadius.circular(12), border: Border.all( - color: AppColors.primary - .withAlpha(50), + color: primaryColor.withAlpha( + 50, + ), width: 1, ), ), @@ -1177,18 +989,18 @@ class _PoetryLevelPageState extends State children: [ Icon( Icons.arrow_back, - color: AppColors - .primary, + color: AppConstants + .primaryColor, size: 20, ), const SizedBox(width: 8), - Text( + const Text( '上一题', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, - color: AppColors.primaryText, + color: Colors.black87, ), ), ], @@ -1203,20 +1015,23 @@ class _PoetryLevelPageState extends State decoration: BoxDecoration( gradient: LinearGradient( colors: [ - AppColors.primary, - AppColors.primary.withAlpha( - 200, - ), + 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: AppColors.primary - .withAlpha(80), + color: primaryColor.withAlpha( + 30, + ), blurRadius: 8, offset: const Offset(0, 4), ), @@ -1225,7 +1040,8 @@ class _PoetryLevelPageState extends State child: ElevatedButton( onPressed: _getHint, style: ElevatedButton.styleFrom( - backgroundColor: Colors.transparent, + backgroundColor: + Colors.transparent, shadowColor: Colors.transparent, padding: const EdgeInsets.symmetric( @@ -1240,9 +1056,10 @@ class _PoetryLevelPageState extends State mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon( + Icon( Icons.lightbulb_outline, - color: Colors.white, + color: AppConstants + .primaryColor, size: 20, ), const SizedBox(width: 8), @@ -1251,8 +1068,8 @@ class _PoetryLevelPageState extends State style: TextStyle( fontSize: 14, fontWeight: - FontWeight.w500, - color: Colors.white, + FontWeight.w600, + color: Colors.black87, ), ), ], @@ -1267,24 +1084,30 @@ class _PoetryLevelPageState extends State decoration: BoxDecoration( gradient: LinearGradient( colors: [ - AppColors.surface, - AppColors.background, + primaryColor, + primaryColor.withAlpha(200), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(12), - border: Border.all( - color: AppColors.primary - .withAlpha(50), - width: 1, - ), + boxShadow: [ + BoxShadow( + color: AppConstants + .primaryColor + .withAlpha(80), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ], ), - child: OutlinedButton( + child: ElevatedButton( onPressed: _nextQuestion, - style: OutlinedButton.styleFrom( - side: BorderSide.none, + style: ElevatedButton.styleFrom( + backgroundColor: + Colors.transparent, + shadowColor: Colors.transparent, padding: const EdgeInsets.symmetric( vertical: 14, @@ -1298,20 +1121,19 @@ class _PoetryLevelPageState extends State mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( + const Text( '下一题', style: TextStyle( fontSize: 14, fontWeight: - FontWeight.w500, - color: AppColors.primaryText, + FontWeight.w600, + color: Colors.white, ), ), const SizedBox(width: 8), Icon( Icons.arrow_forward, - color: AppColors - .primary, + color: Colors.white, size: 20, ), ], @@ -1321,38 +1143,6 @@ class _PoetryLevelPageState extends State ), ], ), - if (_showFeedback && _feedbackMessage != null) - Padding( - padding: const EdgeInsets.only(top: 16), - child: Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: _isAnswerCorrect - ? AppColors.iosGreen - .withAlpha(20) - : AppColors.iosRed - .withAlpha(20), - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: _isAnswerCorrect - ? AppColors.iosGreen - : AppColors.iosRed, - width: 1, - ), - ), - child: Text( - _feedbackMessage!, - style: TextStyle( - color: _isAnswerCorrect - ? AppColors.iosGreen - : AppColors.iosRed, - fontSize: 14, - fontWeight: FontWeight.w500, - ), - textAlign: TextAlign.center, - ), - ), - ), ], ), ), @@ -1361,6 +1151,65 @@ class _PoetryLevelPageState extends State ), ], ), + // 反馈信息气泡(不占用布局) + 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, + ), + ), + ), + ], + ), + ), + ), ], ), ),