Files
wushu/docs/superpowers/plans/2026-04-03-poetry-theme-refactor.md
Developer cba04235c8 release
2026-04-03 03:26:06 +08:00

664 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 诗词答题页面主题色支持与代码重构实现计划
> **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<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,
),
],
),
),
),
),
),
);
});
}
}
```
- [ ] **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<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,
);
}
}
```
- [ ] **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<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,
),
),
],
),
);
});
}
}
```
- [ ] **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<ThemeController>();
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<ThemeController>();
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<ThemeController>();
```
- [ ] **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. **测试充分**:测试所有功能,包括主题色切换、深色模式、关怀模式