# 诗词答题页面主题色支持与代码重构实现计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 将诗词答题相关页面重构以支持动态主题色,并将代码分流到 `poetry-page.dart`,实现 UI 和逻辑的分离。 **Architecture:** 采用组件化分流策略,将 UI 组件(选项、标签、布局)提取到 `poetry-page.dart`,主页面保留状态管理和业务逻辑。使用 GetX 的 `Obx` 进行响应式主题色更新。 **Tech Stack:** Flutter, GetX, ThemeController, ThemeColors --- ## 文件结构 **创建文件:** - `lib/views/profile/level/poetry-page.dart` - UI 组件(选项、标签、布局) **修改文件:** - `lib/views/profile/level/poetry.dart` - 主页面(移除 UI 组件方法,使用新组件) - `lib/views/profile/level/flow-anim.dart` - 流动边框动画(添加主题色支持) - `lib/views/profile/level/distinguish.dart` - 答题记录页面(添加主题色支持) --- ## Task 1: 创建 poetry-page.dart 文件并实现 PoetryOptionItem 组件 **Files:** - Create: `lib/views/profile/level/poetry-page.dart` - [ ] **Step 1: 创建 poetry-page.dart 文件并添加导入** ```dart /// 诗词答题页面组件 library; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../services/get/theme_controller.dart'; import '../../../models/colors/theme_colors.dart'; import '../../../constants/app_constants.dart'; ``` - [ ] **Step 2: 实现 PoetryOptionItem 组件** ```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, ), ], ), ), ), ), ), ); }); } } ``` - [ ] **Step 3: 保存文件** --- ## Task 2: 实现 PoetryOptionsLayout 组件 **Files:** - Modify: `lib/views/profile/level/poetry-page.dart` - [ ] **Step 1: 在 poetry-page.dart 中添加 PoetryOptionsLayout 组件** ```dart /// 选项布局组件 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, ); } } ``` - [ ] **Step 2: 保存文件** --- ## Task 3: 实现 PoetryTag 组件 **Files:** - Modify: `lib/views/profile/level/poetry-page.dart` - [ ] **Step 1: 在 poetry-page.dart 中添加 PoetryTag 组件** ```dart /// 标签组件 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, ), ), ], ), ); }); } } ``` - [ ] **Step 2: 保存文件** --- ## Task 4: 修改 poetry.dart 主页面 **Files:** - Modify: `lib/views/profile/level/poetry.dart` - [ ] **Step 1: 在 poetry.dart 顶部添加导入** 在文件顶部的导入部分添加: ```dart import 'poetry-page.dart'; ``` - [ ] **Step 2: 移除 _buildOptionItem 方法** 删除 `_PoetryLevelPageState` 类中的 `_buildOptionItem` 方法(约 100 行代码)。 - [ ] **Step 3: 移除 _buildOptionsLayout 方法** 删除 `_PoetryLevelPageState` 类中的 `_buildOptionsLayout` 方法(约 40 行代码)。 - [ ] **Step 4: 移除 _buildTag 方法** 删除 `_PoetryLevelPageState` 类中的 `_buildTag` 方法(约 30 行代码)。 - [ ] **Step 5: 替换 _buildOptionsLayout 调用** 在 `build` 方法中,找到原来的 `_buildOptionsLayout()` 调用,替换为: ```dart 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); } }, ) ``` - [ ] **Step 6: 替换 _buildTag 调用** 在 `build` 方法中,找到原来的 `_buildTag()` 调用,替换为: ```dart PoetryTag( label: '类型', value: _currentQuestion!['type']?.toString() ?? '', ) ``` 类似地替换其他标签调用。 - [ ] **Step 7: 添加主题色支持到分数显示** 找到分数显示的 `Container`,将 `AppConstants.primaryColor` 替换为: ```dart final themeController = Get.find(); final primaryColor = ThemeColors.getThemeColor( themeController.themeColorIndexRx.value, ); ``` 然后在 `Container` 的 `decoration` 中使用 `primaryColor`。 - [ ] **Step 8: 保存文件** --- ## Task 5: 修改 flow-anim.dart 添加主题色支持 **Files:** - Modify: `lib/views/profile/level/flow-anim.dart` - [ ] **Step 1: 在 flow-anim.dart 顶部添加导入** ```dart import '../../../models/colors/theme_colors.dart'; ``` - [ ] **Step 2: 修改 FlowingBorderContainer 的 build 方法** 将 `build` 方法修改为: ```dart @override Widget build(BuildContext context) { final themeController = Get.find(); return Obx(() { final color = widget.color ?? ThemeColors.getThemeColor( themeController.themeColorIndexRx.value, ); return AnimatedBuilder( animation: _animation, builder: (context, child) { return Container( padding: EdgeInsets.all(widget.width), decoration: FlowingBorderDecoration( animation: _animation, color: color, width: widget.width, ), child: widget.child, ); }, ); }); } ``` - [ ] **Step 3: 保存文件** --- ## Task 6: 修改 distinguish.dart 添加主题色支持 **Files:** - Modify: `lib/views/profile/level/distinguish.dart` - [ ] **Step 1: 在 distinguish.dart 顶部添加导入** ```dart import '../../../models/colors/theme_colors.dart'; ``` - [ ] **Step 2: 在 _DistinguishPageState 类中添加 ThemeController** 在 `_DistinguishPageState` 类中添加: ```dart final ThemeController _themeController = Get.find(); ``` - [ ] **Step 3: 替换所有 AppConstants.primaryColor 为动态主题色** 在 `build` 方法中,将所有 `AppConstants.primaryColor` 替换为: ```dart final primaryColor = ThemeColors.getThemeColor( _themeController.themeColorIndexRx.value, ); ``` 然后在需要的地方使用 `primaryColor`。 - [ ] **Step 4: 添加深色模式支持** 在 `build` 方法中添加: ```dart final isDark = _themeController.isDarkModeRx.value; ``` 然后根据 `isDark` 调整背景色、文字颜色等。 - [ ] **Step 5: 保存文件** --- ## Task 7: 测试功能 **Files:** - Test: 运行应用并测试所有功能 - [ ] **Step 1: 运行应用** ```bash flutter run ``` - [ ] **Step 2: 测试答题页面** 1. 进入诗词答题页面 2. 点击选项,验证选项点击正常 3. 提交答案,验证反馈显示正常 4. 测试 2x2 和 1x4 布局切换 5. 测试上一题、下一题功能 - [ ] **Step 3: 测试主题色切换** 1. 进入设置页面 2. 切换主题色 3. 返回答题页面,验证所有组件颜色同步更新 - [ ] **Step 4: 测试深色模式** 1. 切换深色模式 2. 验证答题页面颜色正常 3. 验证选项、标签等组件颜色正常 - [ ] **Step 5: 测试关怀模式** 1. 开启关怀模式 2. 进入答题页面 3. 验证底部导航栏不遮挡内容 4. 验证答题功能正常 --- ## Task 8: 提交代码 **Files:** - Commit: 所有修改的文件 - [ ] **Step 1: 检查修改的文件** ```bash git status ``` - [ ] **Step 2: 添加所有修改的文件** ```bash git add lib/views/profile/level/poetry.dart git add lib/views/profile/level/poetry-page.dart git add lib/views/profile/level/flow-anim.dart git add lib/views/profile/level/distinguish.dart ``` - [ ] **Step 3: 提交代码** ```bash git commit -m "feat: 诗词答题页面主题色支持与代码重构 - 创建 poetry-page.dart,提取 UI 组件(PoetryOptionItem、PoetryOptionsLayout、PoetryTag) - 修改 poetry.dart,使用新组件,添加主题色支持 - 修改 flow-anim.dart,添加主题色支持 - 修改 distinguish.dart,添加主题色支持 - 支持动态主题色切换 - 支持深色模式 - 保持页面布局不变" ``` --- ## 注意事项 1. **保持布局不变**:重构过程中不修改页面布局 2. **代码平衡**:确保两个文件代码量相近 3. **性能优化**:使用 `Obx` 进行响应式更新,避免不必要的重建 4. **向后兼容**:确保现有功能不受影响 5. **测试充分**:测试所有功能,包括主题色切换、深色模式、关怀模式