Files
wushu/lib/views/profile/level/distinguish.dart
2026-03-30 21:30:11 +08:00

898 lines
30 KiB
Dart
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.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import '../../../constants/app_constants.dart';
import '../../../controllers/sqlite_storage_controller.dart';
import '../../../controllers/history_controller.dart';
import '../../../services/network_listener_service.dart';
/// 时间: 2026-03-28
/// 功能: 答题记录页面
/// 介绍: 显示用户的诗词答题记录列表,包括题目、标签、是否答对等信息
/// 最新变化: 添加统计数据弹窗功能
class DistinguishPage extends StatefulWidget {
const DistinguishPage({super.key});
@override
State<DistinguishPage> createState() => _DistinguishPageState();
}
class _DistinguishPageState extends State<DistinguishPage> {
// 答题记录列表
List<Map<String, dynamic>> _answerRecords = [];
bool _isLoading = true;
// 统计数据
int _totalQuestions = 0;
int _correctAnswers = 0;
int _wrongAnswers = 0;
double _correctRate = 0.0;
double _wrongRate = 0.0;
double _averageTime = 0.0;
int _hintCount = 0;
int _skipCount = 0;
String _poetryLevel = '未知';
@override
void initState() {
super.initState();
_loadAnswerRecords();
}
/// 加载答题记录
Future<void> _loadAnswerRecords() async {
try {
// 获取答题记录列表
List<String> records = await SQLiteStorageController.getStringList(
'poetryAnswerRecords',
defaultValue: [],
);
// 解析记录
_answerRecords = records
.map((record) {
try {
return jsonDecode(record) as Map<String, dynamic>;
} catch (e) {
print('解析记录失败: $e');
return <String, dynamic>{};
}
})
.where((record) => record.isNotEmpty)
.toList();
// 按时间倒序排列
_answerRecords.sort((a, b) {
final timeA = a['answerTime'] ?? '';
final timeB = b['answerTime'] ?? '';
return timeB.compareTo(timeA);
});
// 加载统计数据
await _loadStatistics();
} catch (e) {
print('加载答题记录失败: $e');
} finally {
setState(() {
_isLoading = false;
});
}
}
/// 加载统计数据
Future<void> _loadStatistics() async {
try {
_totalQuestions = await SQLiteStorageController.getInt(
'totalQuestions',
defaultValue: 0,
);
_correctAnswers = await SQLiteStorageController.getInt(
'correctAnswers',
defaultValue: 0,
);
_wrongAnswers = await SQLiteStorageController.getInt(
'wrongAnswers',
defaultValue: 0,
);
int totalTime = await SQLiteStorageController.getInt(
'totalTime',
defaultValue: 0,
);
_hintCount = await SQLiteStorageController.getInt(
'hintCount',
defaultValue: 0,
);
_skipCount = await SQLiteStorageController.getInt(
'skipCount',
defaultValue: 0,
);
// 计算正确率和错误率
if (_totalQuestions > 0) {
_correctRate = (_correctAnswers / _totalQuestions) * 100;
_wrongRate = (_wrongAnswers / _totalQuestions) * 100;
_averageTime = totalTime / _totalQuestions;
}
// 计算诗词水平
_calculatePoetryLevel();
} catch (e) {
print('加载统计数据失败: $e');
}
}
/// 计算诗词水平
void _calculatePoetryLevel() {
if (_totalQuestions < 5) {
_poetryLevel = '未知(答题数量不足)';
} else if (_correctRate >= 90) {
_poetryLevel = '诗词大师 🏆';
} else if (_correctRate >= 70) {
_poetryLevel = '诗词达人 ⭐';
} else if (_correctRate >= 50) {
_poetryLevel = '诗词爱好者 📚';
} else {
_poetryLevel = '诗词初学者 🌱';
}
}
/// 显示统计弹窗
void _showStatisticsDialog() {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => _buildStatisticsSheet(),
);
}
/// 构建统计弹窗
Widget _buildStatisticsSheet() {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: 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: Colors.grey[300],
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),
),
child: Icon(
Icons.analytics_outlined,
color: AppConstants.primaryColor,
size: 24,
),
),
const SizedBox(width: 12),
const Text(
'本次答题记录',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: 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(
'添加笔记',
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,
),
),
),
],
),
const SizedBox(height: 24),
// 统计卡片
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppConstants.primaryColor.withAlpha(10),
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'),
_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'),
const Divider(height: 24),
_buildStatRow('诗词水平', _poetryLevel, isHighlight: true),
],
),
),
const SizedBox(height: 24),
// 复制按钮
SizedBox(
width: double.infinity,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppConstants.primaryColor,
AppConstants.primaryColor.withAlpha(200),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: AppConstants.primaryColor.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),
],
),
),
),
);
}
/// 构建统计行
Widget _buildStatRow(
String label,
String value, {
bool isGreen = false,
bool isRed = false,
bool isHighlight = false,
}) {
Color valueColor = Colors.black87;
if (isGreen) valueColor = Colors.green;
if (isRed) valueColor = Colors.red;
if (isHighlight) valueColor = AppConstants.primaryColor;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: TextStyle(fontSize: 15, color: Colors.grey[700])),
Text(
value,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: valueColor,
),
),
],
),
);
}
/// 复制统计数据到剪贴板
Future<void> _copyStatisticsToClipboard() async {
String content =
'''
🎓 诗词答题记录评估报告
📊 基础数据统计
━━━━━━━━━━━━━━━━
• 已答题数:$_totalQuestions
• 正确数量:$_correctAnswers
• 错误数量:$_wrongAnswers
• 正确率:${_correctRate.toStringAsFixed(1)}%
• 错误率:${_wrongRate.toStringAsFixed(1)}%
• 平均用时:${_averageTime.toStringAsFixed(1)} 秒/题
📈 辅助数据
━━━━━━━━━━━━━━━━
• 提示次数:$_hintCount
• 跳过次数:$_skipCount
🏆 诗词水平评估
━━━━━━━━━━━━━━━━
$_poetryLevel
💡 AI评估提示
━━━━━━━━━━━━━━━━
请根据以上答题数据,综合评估我的诗词水平,并给出:
1. 当前诗词水平的详细分析
2. 薄弱环节和改进建议
3. 推荐学习的诗词类型或朝代
4. 适合的诗词学习路径建议
感谢您的评估!
''';
await Clipboard.setData(ClipboardData(text: content));
// 关闭弹窗
Navigator.pop(context);
// 显示复制成功提示
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('统计数据已复制可发送给AI进行评估'),
duration: Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
),
);
}
/// 清空答题记录
Future<void> _clearRecords() async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认清空'),
content: const Text('确定要清空所有答题记录吗?此操作不可恢复。'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('确定', style: TextStyle(color: Colors.red)),
),
],
),
);
if (confirmed == true) {
try {
await SQLiteStorageController.setStringList('poetryAnswerRecords', []);
setState(() {
_answerRecords = [];
});
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('答题记录已清空')));
} catch (e) {
print('清空记录失败: $e');
}
}
}
/// 格式化时间
String _formatTime(String? isoTime) {
if (isoTime == null || isoTime.isEmpty) return '未知时间';
try {
final dateTime = DateTime.parse(isoTime);
return DateFormat('MM-dd HH:mm').format(dateTime);
} catch (e) {
return '未知时间';
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(
'答题记录',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
backgroundColor: AppConstants.primaryColor,
foregroundColor: Colors.white,
elevation: 0,
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppConstants.primaryColor,
AppConstants.primaryColor.withAlpha(180),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
),
actions: [
// 统计按钮
IconButton(
onPressed: _showStatisticsDialog,
icon: const Icon(Icons.analytics_outlined),
tooltip: '查看统计',
),
// 清空按钮
if (_answerRecords.isNotEmpty)
IconButton(
onPressed: _clearRecords,
icon: const Icon(Icons.delete_outline),
tooltip: '清空记录',
),
],
),
body: Container(
color: Colors.grey[50],
child: SafeArea(
child: _isLoading
? const Center(child: CircularProgressIndicator())
: _answerRecords.isEmpty
? _buildEmptyView()
: _buildRecordList(),
),
),
);
}
/// 构建空记录视图
Widget _buildEmptyView() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.menu_book_outlined, size: 80, color: Colors.grey[300]),
const SizedBox(height: 16),
Text(
'暂无答题记录',
style: TextStyle(
fontSize: 18,
color: Colors.grey[500],
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 8),
Text(
'快去答题吧!',
style: TextStyle(fontSize: 14, color: Colors.grey[400]),
),
],
),
);
}
/// 构建记录列表
Widget _buildRecordList() {
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) {
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: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: 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: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
'—— $author',
style: TextStyle(
fontSize: 12,
color: 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(20)
: Colors.red.withAlpha(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: Colors.grey[400],
fontStyle: FontStyle.italic,
),
),
),
// 时间
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.access_time, size: 12, color: Colors.grey[400]),
const SizedBox(width: 4),
Text(
answerTime,
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
),
],
),
],
),
],
),
),
);
}
// 写入统计数据到笔记
Future<void> _copyStatisticsContent() async {
try {
// 生成完整的评估报告内容
final StringBuffer content = StringBuffer();
content.writeln('🎓 诗词答题记录评估报告');
content.writeln('');
content.writeln('📊 基础数据统计');
content.writeln('━━━━━━━━━━━━━━━━');
content.writeln('• 已答题数:$_totalQuestions');
content.writeln('• 正确数量:$_correctAnswers');
content.writeln('• 错误数量:$_wrongAnswers');
content.writeln('• 正确率:${_correctRate.toStringAsFixed(1)}%');
content.writeln('• 错误率:${_wrongRate.toStringAsFixed(1)}%');
content.writeln('• 平均用时:${_averageTime.toStringAsFixed(1)} 秒/题');
content.writeln('');
content.writeln('📈 辅助数据');
content.writeln('━━━━━━━━━━━━━━━━');
content.writeln('• 提示次数:$_hintCount');
content.writeln('• 跳过次数:$_skipCount');
content.writeln('');
content.writeln('🏆 诗词水平评估');
content.writeln('━━━━━━━━━━━━━━━━');
content.writeln('$_poetryLevel');
content.writeln('');
content.writeln('💡 AI评估提示');
content.writeln('━━━━━━━━━━━━━━━━');
content.writeln('请根据以上答题数据,综合评估我的诗词水平,并给出:');
content.writeln('1. 当前诗词水平的详细分析');
content.writeln('2. 薄弱环节和改进建议');
content.writeln('3. 推荐学习的诗词类型或朝代');
content.writeln('4. 适合的诗词学习路径建议');
content.writeln('');
content.writeln('感谢您的评估!');
// 保存到笔记
final noteId = await HistoryController.saveNote(
title: '诗词答题评估_${DateFormat('yyyyMMdd_HHmmss').format(DateTime.now())}',
content: content.toString(),
category: '答题评估',
);
if (noteId != null) {
NetworkListenerService().sendSuccessEvent(
NetworkEventType.noteUpdate,
data: noteId,
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('评估报告已保存到笔记'),
duration: Duration(seconds: 2),
),
);
}
}
} catch (e) {
debugPrint('保存笔记失败: $e');
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('保存笔记失败: $e')));
}
}
}
// 从答题记录创建笔记
Future<void> _createNoteFromRecords() async {
try {
// 生成答题记录的详细内容
final StringBuffer content = StringBuffer();
content.writeln('## 答题记录汇总');
content.writeln(
'生成时间: ${DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now())}',
);
content.writeln('总记录数: ${_answerRecords.length}');
content.writeln('');
// 统计信息
int correctCount = 0;
int totalCount = _answerRecords.length;
for (final record in _answerRecords) {
if (record['isCorrect'] == true) {
correctCount++;
}
}
content.writeln('### 统计信息');
content.writeln('- 总题数: $totalCount');
content.writeln('- 答对数: $correctCount');
content.writeln('- 答错数: ${totalCount - correctCount}');
content.writeln(
'- 正确率: ${totalCount > 0 ? (correctCount / totalCount * 100).toStringAsFixed(1) : 0}%',
);
content.writeln('');
content.writeln('### 详细记录');
content.writeln('');
for (int i = 0; i < _answerRecords.length; i++) {
final record = _answerRecords[i];
final question = record['question'] ?? '未知题目';
final userAnswer = record['userAnswer'] ?? '未知答案';
final correctAnswer = record['correctAnswer'] ?? '未知答案';
final isCorrect = record['isCorrect'] == true;
final answerTime = record['answerTime'] ?? '未知时间';
final tags = record['tags'] as List<dynamic>? ?? [];
content.writeln('#### ${i + 1}. $question');
content.writeln('- **你的答案**: $userAnswer');
content.writeln('- **正确答案**: $correctAnswer');
content.writeln('- **答题结果**: ${isCorrect ? '✅ 正确' : '❌ 错误'}');
content.writeln('- **答题时间**: ${_formatTime(answerTime)}');
if (tags.isNotEmpty) {
content.writeln('- **标签**: ${tags.join(', ')}');
}
content.writeln('');
}
final noteId = await HistoryController.saveNote(
title: '答题记录_${DateFormat('yyyyMMdd_HHmmss').format(DateTime.now())}',
content: content.toString(),
category: '答题记录',
);
if (noteId != null) {
NetworkListenerService().sendSuccessEvent(
NetworkEventType.noteUpdate,
data: noteId,
);
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('答题记录已保存到笔记')));
}
}
} catch (e) {
debugPrint('创建笔记失败: $e');
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('创建笔记失败: $e')));
}
}
}
}