关怀模式

This commit is contained in:
Developer
2026-04-02 22:30:49 +08:00
parent 09fee0694c
commit 7872f2e78a
70 changed files with 4884 additions and 2752 deletions

View File

@@ -1,3 +1,8 @@
/// 时间: 2026-03-28
/// 功能: 答题记录页面
/// 介绍: 显示用户的诗词答题记录列表,包括题目、标签、是否答对等信息
/// 最新变化: 添加统计数据弹窗功能,支持主题色设置
import 'dart:convert';
import 'package:flutter/material.dart';
@@ -5,17 +10,12 @@ import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import '../../../constants/app_constants.dart';
import '../../../models/colors/app_colors.dart';
import '../../../controllers/shared_preferences_storage_controller.dart';
import '../../../controllers/history_controller.dart';
import '../../../services/network_listener_service.dart';
import '../../../services/get/theme_controller.dart';
/// 时间: 2026-03-28
/// 功能: 答题记录页面
/// 介绍: 显示用户的诗词答题记录列表,包括题目、标签、是否答对等信息
/// 最新变化: 添加统计数据弹窗功能
class DistinguishPage extends StatefulWidget {
const DistinguishPage({super.key});
@@ -142,239 +142,234 @@ class _DistinguishPageState extends State<DistinguishPage> {
}
void _showStatisticsDialog() {
final isDark = _themeController.isDarkMode;
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => _buildStatisticsSheet(isDark),
builder: (context) => _buildStatisticsSheet(),
);
}
Widget _buildStatisticsSheet(bool isDark) {
return Container(
decoration: BoxDecoration(
color: isDark ? const Color(0xFF2A2A2A) : Colors.white,
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: isDark ? Colors.grey[600] : Colors.grey[300],
borderRadius: BorderRadius.circular(2),
Widget _buildStatisticsSheet() {
return Obx(() {
final isDark = _themeController.isDarkMode;
return Container(
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: AppColors.divider,
borderRadius: BorderRadius.circular(2),
),
),
),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppConstants.primaryColor.withAlpha(20),
borderRadius: BorderRadius.circular(10),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppColors.primary.withAlpha(20),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
Icons.analytics_outlined,
color: AppColors.primary,
size: 24,
),
),
child: Icon(
Icons.analytics_outlined,
color: AppConstants.primaryColor,
size: 24,
),
),
const SizedBox(width: 12),
Text(
'本次答题记录',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: isDark ? Colors.white : Colors.black87,
),
),
],
),
if (_answerRecords.isNotEmpty)
Container(
decoration: BoxDecoration(
color: AppConstants.primaryColor.withAlpha(15),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: AppConstants.primaryColor.withAlpha(50),
width: 0.5,
),
),
child: TextButton.icon(
onPressed: _copyStatisticsContent,
icon: Icon(
Icons.copy,
size: 16,
color: Colors.orange[700],
),
label: Text(
'添加笔记',
const SizedBox(width: 12),
Text(
'本次答题记录',
style: TextStyle(
fontSize: 12,
fontSize: 22,
fontWeight: FontWeight.bold,
color: AppColors.primaryText,
),
),
],
),
if (_answerRecords.isNotEmpty)
Container(
decoration: BoxDecoration(
color: AppColors.primary.withAlpha(15),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: AppColors.primary.withAlpha(50),
width: 0.5,
),
),
child: TextButton.icon(
onPressed: _copyStatisticsContent,
icon: Icon(
Icons.copy,
size: 16,
color: Colors.orange[700],
),
),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
label: Text(
'添加笔记',
style: TextStyle(
fontSize: 12,
color: Colors.orange[700],
),
),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
),
),
],
),
const SizedBox(height: 24),
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppConstants.primaryColor.withAlpha(10),
isDark ? const Color(0xFF2A2A2A) : Colors.white,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: AppConstants.primaryColor.withAlpha(30),
width: 1,
),
),
child: Column(
children: [
_buildStatRow('已答题', '$_totalQuestions', isDark),
_buildStatRow(
'正确',
'$_correctAnswers',
isDark,
isGreen: true,
),
_buildStatRow(
'错误',
'$_wrongAnswers',
isDark,
isRed: true,
),
_buildStatRow(
'正确率',
'${_correctRate.toStringAsFixed(1)}%',
isDark,
isGreen: _correctRate >= 60,
),
_buildStatRow(
'错误率',
'${_wrongRate.toStringAsFixed(1)}%',
isDark,
isRed: _wrongRate > 40,
),
_buildStatRow(
'平均用时',
'${_averageTime.toStringAsFixed(1)}',
isDark,
),
_buildStatRow('提示次数', '$_hintCount', isDark),
_buildStatRow('跳过次数', '$_skipCount', isDark),
Divider(
height: 24,
color: isDark ? Colors.grey[700] : Colors.grey[300],
),
_buildStatRow(
'诗词水平',
_poetryLevel,
isDark,
isHighlight: true,
),
],
),
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: Container(
const SizedBox(height: 24),
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppConstants.primaryColor,
AppConstants.primaryColor.withAlpha(200),
AppColors.primary.withAlpha(10),
AppColors.surface,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: AppConstants.primaryColor.withAlpha(80),
blurRadius: 12,
offset: const Offset(0, 4),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: AppColors.primary.withAlpha(30),
width: 1,
),
),
child: Column(
children: [
_buildStatRow('已答题', '$_totalQuestions'),
_buildStatRow(
'正确',
'$_correctAnswers',
isGreen: true,
),
_buildStatRow(
'错误',
'$_wrongAnswers',
isRed: true,
),
_buildStatRow(
'正确率',
'${_correctRate.toStringAsFixed(1)}%',
isGreen: _correctRate >= 60,
),
_buildStatRow(
'错误率',
'${_wrongRate.toStringAsFixed(1)}%',
isRed: _wrongRate > 40,
),
_buildStatRow(
'平均用时',
'${_averageTime.toStringAsFixed(1)}',
),
_buildStatRow('提示次数', '$_hintCount'),
_buildStatRow('跳过次数', '$_skipCount'),
Divider(
height: 24,
color: AppColors.divider,
),
_buildStatRow(
'诗词水平',
_poetryLevel,
isHighlight: true,
),
],
),
child: ElevatedButton(
onPressed: _copyStatisticsToClipboard,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppColors.primary,
AppColors.primary.withAlpha(200),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.copy, color: Colors.white, size: 20),
SizedBox(width: 8),
Text(
'复制数据发送给AI评估',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: AppColors.primary.withAlpha(80),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: ElevatedButton(
onPressed: _copyStatisticsToClipboard,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.copy, color: Colors.white, size: 20),
SizedBox(width: 8),
Text(
'复制数据发送给AI评估',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
],
),
),
),
),
),
const SizedBox(height: 16),
],
const SizedBox(height: 16),
],
),
),
),
),
);
);
});
}
Widget _buildStatRow(
String label,
String value,
bool isDark, {
String value, {
bool isGreen = false,
bool isRed = false,
bool isHighlight = false,
}) {
Color valueColor = isDark ? Colors.white : Colors.black87;
if (isGreen) valueColor = Colors.green;
if (isRed) valueColor = Colors.red;
if (isHighlight) valueColor = AppConstants.primaryColor;
Color valueColor = AppColors.primaryText;
if (isGreen) valueColor = AppColors.iosGreen;
if (isRed) valueColor = AppColors.iosRed;
if (isHighlight) valueColor = AppColors.primary;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
@@ -385,7 +380,7 @@ class _DistinguishPageState extends State<DistinguishPage> {
label,
style: TextStyle(
fontSize: 15,
color: isDark ? Colors.grey[400] : Colors.grey[700],
color: AppColors.secondaryText,
),
),
Text(
@@ -456,16 +451,17 @@ $_poetryLevel
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认清空'),
content: const Text('确定要清空所有答题记录吗?此操作不可恢复。'),
backgroundColor: AppColors.surface,
title: Text('确认清空', style: TextStyle(color: AppColors.primaryText)),
content: Text('确定要清空所有答题记录吗?此操作不可恢复。', style: TextStyle(color: AppColors.secondaryText)),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
child: Text('取消', style: TextStyle(color: AppColors.primary)),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('确定', style: TextStyle(color: Colors.red)),
child: Text('确定', style: TextStyle(color: AppColors.iosRed)),
),
],
),
@@ -511,18 +507,18 @@ $_poetryLevel
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
color: isDark ? Colors.white : Colors.white,
color: Colors.white,
),
),
backgroundColor: AppConstants.primaryColor,
backgroundColor: AppColors.primary,
foregroundColor: Colors.white,
elevation: 0,
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppConstants.primaryColor,
AppConstants.primaryColor.withAlpha(180),
AppColors.primary,
AppColors.primary.withAlpha(180),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
@@ -544,239 +540,246 @@ $_poetryLevel
],
),
body: Container(
color: isDark ? const Color(0xFF1A1A1A) : Colors.grey[50],
color: AppColors.background,
child: SafeArea(
child: _isLoading
? const Center(child: CircularProgressIndicator())
: _answerRecords.isEmpty
? _buildEmptyView(isDark)
: _buildRecordList(isDark),
? _buildEmptyView()
: _buildRecordList(),
),
),
);
});
}
Widget _buildEmptyView(bool isDark) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.menu_book_outlined,
size: 80,
color: isDark ? Colors.grey[600] : Colors.grey[300],
),
const SizedBox(height: 16),
Text(
'暂无答题记录',
style: TextStyle(
fontSize: 18,
color: isDark ? Colors.grey[400] : Colors.grey[500],
fontWeight: FontWeight.w500,
Widget _buildEmptyView() {
return Obx(() {
final isDark = _themeController.isDarkMode;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.menu_book_outlined,
size: 80,
color: AppColors.tertiaryText,
),
),
const SizedBox(height: 8),
Text(
'快去答题吧!',
style: TextStyle(
fontSize: 14,
color: isDark ? Colors.grey[500] : Colors.grey[400],
const SizedBox(height: 16),
Text(
'暂无答题记录',
style: TextStyle(
fontSize: 18,
color: AppColors.secondaryText,
fontWeight: FontWeight.w500,
),
),
),
],
),
);
const SizedBox(height: 8),
Text(
'快去答题吧!',
style: TextStyle(
fontSize: 14,
color: AppColors.tertiaryText,
),
),
],
),
);
});
}
Widget _buildRecordList(bool isDark) {
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _answerRecords.length,
itemBuilder: (context, index) {
final record = _answerRecords[index];
return _buildRecordCard(record, index, isDark);
},
);
Widget _buildRecordList() {
return Obx(() {
final isDark = _themeController.isDarkMode;
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _answerRecords.length,
itemBuilder: (context, index) {
final record = _answerRecords[index];
return _buildRecordCard(record, index);
},
);
});
}
Widget _buildRecordCard(Map<String, dynamic> record, int index, bool isDark) {
Widget _buildRecordCard(Map<String, dynamic> record, int index) {
final question = record['question'] ?? '未知题目';
final author = record['author'] ?? '未知作者';
final tags = (record['tags'] as List<dynamic>?)?.cast<String>() ?? [];
final isCorrect = record['isCorrect'] ?? false;
final answerTime = _formatTime(record['answerTime']);
return Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF2A2A2A) : Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: isDark
? Colors.black.withAlpha(30)
: Colors.black.withAlpha(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: AppConstants.primaryColor.withAlpha(20),
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Text(
'${index + 1}',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: AppConstants.primaryColor,
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
question,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: isDark ? Colors.white : Colors.black87,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
'—— $author',
style: TextStyle(
fontSize: 12,
color: isDark ? Colors.grey[400] : Colors.grey[600],
fontStyle: FontStyle.italic,
),
),
],
),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 4,
),
decoration: BoxDecoration(
color: isCorrect
? Colors.green.withAlpha(isDark ? 40 : 20)
: Colors.red.withAlpha(isDark ? 40 : 20),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isCorrect ? Icons.check_circle : Icons.cancel,
size: 14,
color: isCorrect ? Colors.green : Colors.red,
),
const SizedBox(width: 4),
Text(
isCorrect ? '答对' : '答错',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: isCorrect ? Colors.green : Colors.red,
),
),
],
),
),
],
),
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: AppConstants.primaryColor.withAlpha(15),
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: AppConstants.primaryColor.withAlpha(
50,
),
width: 0.5,
),
),
child: Text(
tag,
style: TextStyle(
fontSize: 11,
color: AppConstants.primaryColor,
fontWeight: FontWeight.w500,
),
),
);
}).toList(),
)
: Text(
'暂无标签',
style: TextStyle(
fontSize: 12,
color: isDark ? Colors.grey[500] : Colors.grey[400],
fontStyle: FontStyle.italic,
),
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.access_time,
size: 12,
color: isDark ? Colors.grey[500] : Colors.grey[400],
),
const SizedBox(width: 4),
Text(
answerTime,
style: TextStyle(
fontSize: 12,
color: isDark ? Colors.grey[400] : Colors.grey[500],
),
),
],
),
],
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,
),
),
),
),
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,
children: [
Icon(
Icons.access_time,
size: 12,
color: AppColors.tertiaryText,
),
const SizedBox(width: 4),
Text(
answerTime,
style: TextStyle(
fontSize: 12,
color: AppColors.secondaryText,
),
),
],
),
],
),
],
),
),
);
});
}
// 写入统计数据到笔记