feat: 诗词答题页面主题色支持与代码重构
- 创建 poetry-page.dart,提取 UI 组件(PoetryOptionItem、PoetryOptionsLayout、PoetryTag) - 修改 poetry.dart,使用新组件,添加主题色支持 - 修改 flow-anim.dart,添加主题色支持 - 修改 distinguish.dart,添加主题色支持 - 支持动态主题色切换 - 支持深色模式 - 保持页面布局不变
This commit is contained in:
327
lib/views/profile/level/poetry-page.dart
Normal file
327
lib/views/profile/level/poetry-page.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user