feat: 诗词答题页面主题色支持与代码重构

- 创建 poetry-page.dart,提取 UI 组件(PoetryOptionItem、PoetryOptionsLayout、PoetryTag)
- 修改 poetry.dart,使用新组件,添加主题色支持
- 修改 flow-anim.dart,添加主题色支持
- 修改 distinguish.dart,添加主题色支持
- 支持动态主题色切换
- 支持深色模式
- 保持页面布局不变
This commit is contained in:
Developer
2026-04-03 00:38:17 +08:00
parent 7872f2e78a
commit c12f26bd4d
4 changed files with 790 additions and 612 deletions

View File

@@ -11,6 +11,7 @@ import 'package:get/get.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import '../../../models/colors/app_colors.dart'; import '../../../models/colors/app_colors.dart';
import '../../../models/colors/theme_colors.dart';
import '../../../controllers/shared_preferences_storage_controller.dart'; import '../../../controllers/shared_preferences_storage_controller.dart';
import '../../../controllers/history_controller.dart'; import '../../../controllers/history_controller.dart';
import '../../../services/network_listener_service.dart'; import '../../../services/network_listener_service.dart';
@@ -262,16 +263,8 @@ class _DistinguishPageState extends State<DistinguishPage> {
child: Column( child: Column(
children: [ children: [
_buildStatRow('已答题', '$_totalQuestions'), _buildStatRow('已答题', '$_totalQuestions'),
_buildStatRow( _buildStatRow('正确', '$_correctAnswers', isGreen: true),
'正确', _buildStatRow('错误', '$_wrongAnswers', isRed: true),
'$_correctAnswers',
isGreen: true,
),
_buildStatRow(
'错误',
'$_wrongAnswers',
isRed: true,
),
_buildStatRow( _buildStatRow(
'正确率', '正确率',
'${_correctRate.toStringAsFixed(1)}%', '${_correctRate.toStringAsFixed(1)}%',
@@ -288,15 +281,8 @@ class _DistinguishPageState extends State<DistinguishPage> {
), ),
_buildStatRow('提示次数', '$_hintCount'), _buildStatRow('提示次数', '$_hintCount'),
_buildStatRow('跳过次数', '$_skipCount'), _buildStatRow('跳过次数', '$_skipCount'),
Divider( Divider(height: 24, color: AppColors.divider),
height: 24, _buildStatRow('诗词水平', _poetryLevel, isHighlight: true),
color: AppColors.divider,
),
_buildStatRow(
'诗词水平',
_poetryLevel,
isHighlight: true,
),
], ],
), ),
), ),
@@ -378,10 +364,7 @@ class _DistinguishPageState extends State<DistinguishPage> {
children: [ children: [
Text( Text(
label, label,
style: TextStyle( style: TextStyle(fontSize: 15, color: AppColors.secondaryText),
fontSize: 15,
color: AppColors.secondaryText,
),
), ),
Text( Text(
value, value,
@@ -453,7 +436,10 @@ $_poetryLevel
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
backgroundColor: AppColors.surface, backgroundColor: AppColors.surface,
title: Text('确认清空', style: TextStyle(color: AppColors.primaryText)), title: Text('确认清空', style: TextStyle(color: AppColors.primaryText)),
content: Text('确定要清空所有答题记录吗?此操作不可恢复。', style: TextStyle(color: AppColors.secondaryText)), content: Text(
'确定要清空所有答题记录吗?此操作不可恢复。',
style: TextStyle(color: AppColors.secondaryText),
),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(context, false), onPressed: () => Navigator.pop(context, false),
@@ -500,6 +486,9 @@ $_poetryLevel
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Obx(() { return Obx(() {
final isDark = _themeController.isDarkMode; final isDark = _themeController.isDarkMode;
final primaryColor = ThemeColors.getThemeColor(
_themeController.themeColorIndexRx.value,
);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(
@@ -510,16 +499,13 @@ $_poetryLevel
color: Colors.white, color: Colors.white,
), ),
), ),
backgroundColor: AppColors.primary, backgroundColor: primaryColor,
foregroundColor: Colors.white, foregroundColor: Colors.white,
elevation: 0, elevation: 0,
flexibleSpace: Container( flexibleSpace: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [primaryColor, primaryColor.withAlpha(180)],
AppColors.primary,
AppColors.primary.withAlpha(180),
],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
), ),
@@ -540,7 +526,7 @@ $_poetryLevel
], ],
), ),
body: Container( body: Container(
color: AppColors.background, color: isDark ? const Color(0xFF1A1A1A) : AppColors.background,
child: SafeArea( child: SafeArea(
child: _isLoading child: _isLoading
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
@@ -577,10 +563,7 @@ $_poetryLevel
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'快去答题吧!', '快去答题吧!',
style: TextStyle( style: TextStyle(fontSize: 14, color: AppColors.tertiaryText),
fontSize: 14,
color: AppColors.tertiaryText,
),
), ),
], ],
), ),
@@ -591,30 +574,36 @@ $_poetryLevel
Widget _buildRecordList() { Widget _buildRecordList() {
return Obx(() { return Obx(() {
final isDark = _themeController.isDarkMode; final isDark = _themeController.isDarkMode;
final primaryColor = ThemeColors.getThemeColor(
_themeController.themeColorIndexRx.value,
);
return ListView.builder( return ListView.builder(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
itemCount: _answerRecords.length, itemCount: _answerRecords.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final record = _answerRecords[index]; final record = _answerRecords[index];
return _buildRecordCard(record, index); return _buildRecordCard(record, index, primaryColor, isDark);
}, },
); );
}); });
} }
Widget _buildRecordCard(Map<String, dynamic> record, int index) { Widget _buildRecordCard(
Map<String, dynamic> record,
int index,
Color primaryColor,
bool isDark,
) {
final question = record['question'] ?? '未知题目'; final question = record['question'] ?? '未知题目';
final author = record['author'] ?? '未知作者'; final author = record['author'] ?? '未知作者';
final tags = (record['tags'] as List<dynamic>?)?.cast<String>() ?? []; final tags = (record['tags'] as List<dynamic>?)?.cast<String>() ?? [];
final isCorrect = record['isCorrect'] ?? false; final isCorrect = record['isCorrect'] ?? false;
final answerTime = _formatTime(record['answerTime']); final answerTime = _formatTime(record['answerTime']);
return Obx(() {
final isDark = _themeController.isDarkMode;
return Container( return Container(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.surface, color: isDark ? const Color(0xFF2A2A2A) : AppColors.surface,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@@ -636,7 +625,7 @@ $_poetryLevel
width: 28, width: 28,
height: 28, height: 28,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primary.withAlpha(20), color: primaryColor.withAlpha(20),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Center( child: Center(
@@ -645,7 +634,7 @@ $_poetryLevel
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: AppColors.primary, color: primaryColor,
), ),
), ),
), ),
@@ -660,7 +649,9 @@ $_poetryLevel
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.primaryText, color: isDark
? Colors.grey[300]
: AppColors.primaryText,
), ),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -670,7 +661,9 @@ $_poetryLevel
'—— $author', '—— $author',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.secondaryText, color: isDark
? Colors.grey[500]
: AppColors.secondaryText,
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
), ),
), ),
@@ -695,7 +688,9 @@ $_poetryLevel
Icon( Icon(
isCorrect ? Icons.check_circle : Icons.cancel, isCorrect ? Icons.check_circle : Icons.cancel,
size: 14, size: 14,
color: isCorrect ? AppColors.iosGreen : AppColors.iosRed, color: isCorrect
? AppColors.iosGreen
: AppColors.iosRed,
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
@@ -703,7 +698,9 @@ $_poetryLevel
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: isCorrect ? AppColors.iosGreen : AppColors.iosRed, color: isCorrect
? AppColors.iosGreen
: AppColors.iosRed,
), ),
), ),
], ],
@@ -726,12 +723,10 @@ $_poetryLevel
vertical: 3, vertical: 3,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primary.withAlpha(15), color: primaryColor.withAlpha(15),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
border: Border.all( border: Border.all(
color: AppColors.primary.withAlpha( color: primaryColor.withAlpha(50),
50,
),
width: 0.5, width: 0.5,
), ),
), ),
@@ -739,7 +734,7 @@ $_poetryLevel
tag, tag,
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
color: AppColors.primary, color: primaryColor,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -750,7 +745,9 @@ $_poetryLevel
'暂无标签', '暂无标签',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.tertiaryText, color: isDark
? Colors.grey[500]
: AppColors.tertiaryText,
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
), ),
), ),
@@ -761,14 +758,16 @@ $_poetryLevel
Icon( Icon(
Icons.access_time, Icons.access_time,
size: 12, size: 12,
color: AppColors.tertiaryText, color: isDark ? Colors.grey[500] : AppColors.tertiaryText,
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
answerTime, answerTime,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.secondaryText, color: isDark
? Colors.grey[400]
: AppColors.secondaryText,
), ),
), ),
], ],
@@ -779,7 +778,6 @@ $_poetryLevel
), ),
), ),
); );
});
} }
// 写入统计数据到笔记 // 写入统计数据到笔记

View File

@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../services/get/theme_controller.dart'; import '../../../services/get/theme_controller.dart';
import '../../../models/colors/theme_colors.dart';
/// 流动边框装饰器 /// 流动边框装饰器
class FlowingBorderDecoration extends Decoration { class FlowingBorderDecoration extends Decoration {
@@ -108,7 +109,10 @@ class _FlowingBorderContainerState extends State<FlowingBorderContainer>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Obx(() { return Obx(() {
final color = widget.color ?? _themeController.currentThemeColor; final color =
widget.color ??
ThemeColors.getThemeColor(_themeController.themeColorIndexRx.value);
return AnimatedBuilder( return AnimatedBuilder(
animation: _animation, animation: _animation,
builder: (context, child) { builder: (context, child) {

View File

@@ -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<ThemeController>();
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<dynamic> 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<Widget> 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<ThemeController>();
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,
),
),
],
),
);
});
}
}

View File

@@ -1,25 +1,34 @@
/// 时间: 2026-03-28
/// 功能: 诗词答题页面
/// 介绍: 基于 API 接口实现的诗词答题系统,支持获取题目、提交答案、获取提示
/// 最新变化: 添加自动加载下一题开关,隐藏提示标签,使用独立逻辑管理器,支持主题色设置
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.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 '../../../controllers/shared_preferences_storage_controller.dart';
import '../../../services/get/theme_controller.dart'; import '../../../services/get/theme_controller.dart';
import '../../../models/colors/theme_colors.dart';
import '../guide/tongji.dart'; import '../guide/tongji.dart';
import 'level-jilu.dart'; import 'level-jilu.dart';
import 'flow-anim.dart'; import 'flow-anim.dart';
import 'distinguish.dart'; import 'distinguish.dart';
import 'poetry-page.dart';
import '../settings/offline-data.dart'; import '../settings/offline-data.dart';
/// 时间: 2026-03-28
/// 功能: 诗词答题页面
/// 介绍: 基于 API 接口实现的诗词答题系统,支持获取题目、提交答案、获取提示
/// 最新变化: 添加自动加载下一题开关,隐藏提示标签,使用独立逻辑管理器
class PoetryLevelPage extends StatefulWidget { 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 @override
State<PoetryLevelPage> createState() => _PoetryLevelPageState(); State<PoetryLevelPage> createState() => _PoetryLevelPageState();
@@ -137,7 +146,9 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(initResult.message ?? '已加载离线缓存'), content: Text(initResult.message ?? '已加载离线缓存'),
backgroundColor: AppColors.primary, backgroundColor: ThemeColors.getThemeColor(
_themeController.themeColorIndexRx.value,
),
duration: const Duration(seconds: 2), duration: const Duration(seconds: 2),
), ),
); );
@@ -153,15 +164,14 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
backgroundColor: AppColors.surface, title: const Text('提示'),
title: Text('提示', style: TextStyle(color: AppColors.primaryText)), content: const Text('当前无网络连接且无离线缓存数据,请先下载数据或检查网络设置。'),
content: Text('当前无网络连接且无离线缓存数据,请先下载数据或检查网络设置。', style: TextStyle(color: AppColors.secondaryText)),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: Text('取消', style: TextStyle(color: AppColors.primary)), child: const Text('取消'),
), ),
TextButton( TextButton(
onPressed: () { onPressed: () {
@@ -172,7 +182,7 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
MaterialPageRoute(builder: (_) => const OfflineDataPage()), MaterialPageRoute(builder: (_) => const OfflineDataPage()),
); );
}, },
child: Text('去下载', style: TextStyle(color: AppColors.primary)), child: const Text('去下载'),
), ),
], ],
); );
@@ -407,254 +417,16 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
); );
} }
/// 构建选项布局
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<Widget> 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;
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),
),
]
: [
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,
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,
),
child: Center(
child: Text(
'$optionNum',
style: TextStyle(
color: isSelected
? (isCorrect
? AppColors.iosGreen
: isWrong
? AppColors.iosRed
: AppColors.primary)
: AppColors.primary,
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 : AppColors.primaryText,
),
),
),
if (isSelected)
Icon(
isCorrect
? Icons.check_circle
: isWrong
? Icons.cancel
: Icons.radio_button_checked,
color: Colors.white,
size: 28,
),
],
),
),
),
),
),
);
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Obx(() { return Obx(() {
final isDark = _themeController.isDarkMode; final isDark = _themeController.isDarkModeRx.value;
final primaryColor = ThemeColors.getThemeColor(
_themeController.themeColorIndexRx.value,
);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: widget.showAppBar
? AppBar(
title: Text( title: Text(
'诗词答题', '诗词答题',
style: TextStyle( style: TextStyle(
@@ -663,16 +435,14 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
color: Colors.white, color: Colors.white,
), ),
), ),
backgroundColor: AppColors.primary, backgroundColor: primaryColor,
foregroundColor: Colors.white, foregroundColor: Colors.white,
elevation: 0, elevation: 0,
leading: widget.showBackButton ? null : const SizedBox.shrink(),
flexibleSpace: Container( flexibleSpace: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [primaryColor, primaryColor.withAlpha(180)],
AppColors.primary,
AppColors.primary.withAlpha(180),
],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
), ),
@@ -708,16 +478,17 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
), ),
), ),
], ],
), )
: null,
body: Container( body: Container(
color: AppColors.background, color: isDark ? const Color(0xFF1A1A1A) : Colors.white,
child: SafeArea( child: SafeArea(
child: Padding( child: Padding(
padding: const EdgeInsets.only( padding: EdgeInsets.only(
left: 16.0, left: 16.0,
right: 16.0, right: 16.0,
top: 8.0, top: 8.0,
bottom: 16.0, bottom: widget.showAppBar ? 16.0 : 100.0, // 关怀模式下增加底部padding
), ),
child: Stack( child: Stack(
children: [ children: [
@@ -740,8 +511,8 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [
AppColors.primary, primaryColor,
AppColors.primary.withAlpha(200), primaryColor.withAlpha(200),
], ],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
@@ -749,9 +520,7 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColors.primary.withAlpha( color: primaryColor.withAlpha(80),
80,
),
blurRadius: 12, blurRadius: 12,
offset: const Offset(0, 4), offset: const Offset(0, 4),
), ),
@@ -844,7 +613,7 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
children: [ children: [
CircularProgressIndicator( CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>( valueColor: AlwaysStoppedAnimation<Color>(
AppColors.primary, primaryColor,
), ),
strokeWidth: 3, strokeWidth: 3,
), ),
@@ -853,7 +622,9 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
'加载题目中...', '加载题目中...',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: AppColors.tertiaryText, color: isDark
? Colors.grey[400]
: Colors.grey,
), ),
), ),
], ],
@@ -871,14 +642,14 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDark color: isDark
? AppColors.iosRed.withAlpha(30) ? Colors.red[900]!.withAlpha(30)
: AppColors.iosRed.withAlpha(10), : Colors.red[50],
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Icon( child: Icon(
Icons.error_outline, Icons.error_outline,
size: 64, size: 64,
color: AppColors.iosRed, color: Colors.red[400],
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
@@ -887,7 +658,9 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: AppColors.iosRed, color: isDark
? Colors.red[300]
: Colors.red,
), ),
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
@@ -895,17 +668,14 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [
AppColors.primary, primaryColor,
AppColors.primary.withAlpha( primaryColor.withAlpha(200),
200,
),
], ],
), ),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColors.primary color: primaryColor.withAlpha(80),
.withAlpha(80),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 4), offset: const Offset(0, 4),
), ),
@@ -966,9 +736,15 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
BorderRadius.circular( BorderRadius.circular(
16, 16,
), ),
color: AppColors.surface, color: isDark
? const Color(
0xFF2A2A2A,
)
: Colors.white,
), ),
), ),
color: AppConstants
.primaryColor,
width: 4, width: 4,
), ),
), ),
@@ -979,10 +755,19 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [
AppColors.surface, isDark
AppColors.primary ? const Color(
0xFF2A2A2A,
)
: Colors.white,
AppConstants
.primaryColor
.withAlpha(5), .withAlpha(5),
AppColors.surface, isDark
? const Color(
0xFF2A2A2A,
)
: Colors.white,
], ],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: end:
@@ -1007,8 +792,8 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
width: 4, width: 4,
height: 20, height: 20,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors color: AppConstants
.primary, .primaryColor,
borderRadius: borderRadius:
BorderRadius.circular( BorderRadius.circular(
2, 2,
@@ -1025,8 +810,8 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
fontWeight: fontWeight:
FontWeight FontWeight
.w600, .w600,
color: AppColors color: AppConstants
.primary, .primaryColor,
), ),
), ),
], ],
@@ -1042,7 +827,9 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
fontWeight: fontWeight:
FontWeight.bold, FontWeight.bold,
height: 1.5, height: 1.5,
color: AppColors.primaryText, color: isDark
? Colors.white
: Colors.black87,
), ),
), ),
// 标签信息 // 标签信息
@@ -1064,8 +851,8 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
12, 12,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors color: AppConstants
.primary .primaryColor
.withAlpha( .withAlpha(
10, 10,
), ),
@@ -1079,23 +866,27 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
MainAxisAlignment MainAxisAlignment
.spaceBetween, .spaceBetween,
children: [ children: [
_buildTag( PoetryTag(
'作者', label: '作者',
value:
_currentQuestion!['author'] ?? _currentQuestion!['author'] ??
'', '',
), ),
_buildTag( PoetryTag(
'年代', label: '年代',
value:
_currentQuestion!['dynasty'] ?? _currentQuestion!['dynasty'] ??
'', '',
), ),
_buildTag( PoetryTag(
'类型', label: '类型',
value:
_currentQuestion!['type'] ?? _currentQuestion!['type'] ??
'', '',
), ),
_buildTag( PoetryTag(
'阶段', label: '阶段',
value:
_currentQuestion!['grade'] ?? _currentQuestion!['grade'] ??
'', '',
), ),
@@ -1113,7 +904,27 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
), ),
const SizedBox(height: 10), 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<PoetryLevelPage>
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.surface, color: Colors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@@ -1144,8 +955,8 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [
AppColors.surface, Colors.white,
AppColors.background, Colors.grey[50]!,
], ],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
@@ -1153,8 +964,9 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
borderRadius: borderRadius:
BorderRadius.circular(12), BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: AppColors.primary color: primaryColor.withAlpha(
.withAlpha(50), 50,
),
width: 1, width: 1,
), ),
), ),
@@ -1177,18 +989,18 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
children: [ children: [
Icon( Icon(
Icons.arrow_back, Icons.arrow_back,
color: AppColors color: AppConstants
.primary, .primaryColor,
size: 20, size: 20,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( const Text(
'上一题', '上一题',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: fontWeight:
FontWeight.w500, FontWeight.w500,
color: AppColors.primaryText, color: Colors.black87,
), ),
), ),
], ],
@@ -1203,20 +1015,23 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [
AppColors.primary, Colors.white,
AppColors.primary.withAlpha( Colors.grey[50]!,
200,
),
], ],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
), ),
borderRadius: borderRadius:
BorderRadius.circular(12), BorderRadius.circular(12),
border: Border.all(
color: primaryColor,
width: 2,
),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColors.primary color: primaryColor.withAlpha(
.withAlpha(80), 30,
),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 4), offset: const Offset(0, 4),
), ),
@@ -1225,7 +1040,8 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
child: ElevatedButton( child: ElevatedButton(
onPressed: _getHint, onPressed: _getHint,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent, backgroundColor:
Colors.transparent,
shadowColor: Colors.transparent, shadowColor: Colors.transparent,
padding: padding:
const EdgeInsets.symmetric( const EdgeInsets.symmetric(
@@ -1240,9 +1056,10 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
mainAxisAlignment: mainAxisAlignment:
MainAxisAlignment.center, MainAxisAlignment.center,
children: [ children: [
const Icon( Icon(
Icons.lightbulb_outline, Icons.lightbulb_outline,
color: Colors.white, color: AppConstants
.primaryColor,
size: 20, size: 20,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@@ -1251,8 +1068,8 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: fontWeight:
FontWeight.w500, FontWeight.w600,
color: Colors.white, color: Colors.black87,
), ),
), ),
], ],
@@ -1267,24 +1084,30 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [
AppColors.surface, primaryColor,
AppColors.background, primaryColor.withAlpha(200),
], ],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
), ),
borderRadius: borderRadius:
BorderRadius.circular(12), BorderRadius.circular(12),
border: Border.all( boxShadow: [
color: AppColors.primary BoxShadow(
.withAlpha(50), color: AppConstants
width: 1, .primaryColor
.withAlpha(80),
blurRadius: 12,
offset: const Offset(0, 4),
), ),
],
), ),
child: OutlinedButton( child: ElevatedButton(
onPressed: _nextQuestion, onPressed: _nextQuestion,
style: OutlinedButton.styleFrom( style: ElevatedButton.styleFrom(
side: BorderSide.none, backgroundColor:
Colors.transparent,
shadowColor: Colors.transparent,
padding: padding:
const EdgeInsets.symmetric( const EdgeInsets.symmetric(
vertical: 14, vertical: 14,
@@ -1298,20 +1121,19 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
mainAxisAlignment: mainAxisAlignment:
MainAxisAlignment.center, MainAxisAlignment.center,
children: [ children: [
Text( const Text(
'下一题', '下一题',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: fontWeight:
FontWeight.w500, FontWeight.w600,
color: AppColors.primaryText, color: Colors.white,
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Icon( Icon(
Icons.arrow_forward, Icons.arrow_forward,
color: AppColors color: Colors.white,
.primary,
size: 20, size: 20,
), ),
], ],
@@ -1321,45 +1143,72 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
), ),
], ],
), ),
],
),
),
],
),
),
],
),
// 反馈信息气泡(不占用布局)
if (_showFeedback && _feedbackMessage != null) if (_showFeedback && _feedbackMessage != null)
Padding( Positioned(
padding: const EdgeInsets.only(top: 16), top: 0,
child: Container( left: 16,
padding: const EdgeInsets.all(12), 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( decoration: BoxDecoration(
color: _isAnswerCorrect gradient: LinearGradient(
? AppColors.iosGreen colors: _isAnswerCorrect
.withAlpha(20) ? [Colors.green[400]!, Colors.green[300]!]
: AppColors.iosRed : [Colors.orange[400]!, Colors.orange[300]!],
.withAlpha(20), begin: Alignment.topLeft,
borderRadius: BorderRadius.circular(8), end: Alignment.bottomRight,
border: Border.all(
color: _isAnswerCorrect
? AppColors.iosGreen
: AppColors.iosRed,
width: 1,
), ),
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( child: Text(
_feedbackMessage!, _feedbackMessage!,
style: TextStyle( style: const TextStyle(
color: _isAnswerCorrect
? AppColors.iosGreen
: AppColors.iosRed,
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w600,
), color: Colors.white,
textAlign: TextAlign.center,
), ),
), ),
), ),
], ],
), ),
), ),
],
),
),
],
), ),
], ],
), ),