Initial commit: Flutter 无书应用项目

This commit is contained in:
Developer
2026-03-30 02:35:31 +08:00
commit 9175ff9905
566 changed files with 103261 additions and 0 deletions

View File

@@ -0,0 +1,706 @@
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';
/// 时间: 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(
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,
),
),
],
),
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]),
),
],
),
],
),
],
),
),
);
}
}