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,548 @@
/// 时间: 2025-03-21
/// 功能: 历史记录页面
/// 介绍: 独立的历史记录管理页面,支持查看、搜索、删除和导出功能
/// 最新变化: 新创建文件,实现历史记录的独立页面功能
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../constants/app_constants.dart';
import '../../controllers/history_controller.dart';
import '../../utils/flutter_compatibility_fix.dart';
class HistoryPage extends StatefulWidget {
const HistoryPage({super.key});
@override
State<HistoryPage> createState() => _HistoryPageState();
}
class _HistoryPageState extends State<HistoryPage> {
List<Map<String, dynamic>> _historyList = [];
List<Map<String, dynamic>> _filteredHistoryList = [];
bool _isLoading = true;
String _searchKeyword = '';
int _selectedSortType = 0; // 0: 时间倒序, 1: 时间正序, 2: 按名称排序
final List<String> _sortTypes = ['时间倒序', '时间正序', '按名称排序'];
@override
void initState() {
super.initState();
_loadHistory();
}
// === 加载历史记录 ===
Future<void> _loadHistory() async {
setState(() {
_isLoading = true;
});
try {
final history = await HistoryController.getHistory();
setState(() {
_historyList = history;
_filteredHistoryList = history;
_isLoading = false;
});
} catch (e) {
print('加载历史记录失败: $e');
setState(() {
_isLoading = false;
});
}
}
// === 搜索历史记录 ===
void _searchHistory(String keyword) {
setState(() {
_searchKeyword = keyword;
});
if (keyword.isEmpty) {
setState(() {
_filteredHistoryList = _historyList;
});
return;
}
_performSearch(keyword);
}
Future<void> _performSearch(String keyword) async {
try {
final searchResults = await HistoryController.searchHistory(keyword);
setState(() {
_filteredHistoryList = searchResults;
});
} catch (e) {
print('搜索历史记录失败: $e');
}
}
// === 排序历史记录 ===
void _sortHistory(int sortType) {
setState(() {
_selectedSortType = sortType;
});
final sortedList = List<Map<String, dynamic>>.from(_filteredHistoryList);
switch (sortType) {
case 0: // 时间倒序
sortedList.sort((a, b) =>
(b['timestamp'] ?? 0).compareTo(a['timestamp'] ?? 0));
break;
case 1: // 时间正序
sortedList.sort((a, b) =>
(a['timestamp'] ?? 0).compareTo(b['timestamp'] ?? 0));
break;
case 2: // 按名称排序
sortedList.sort((a, b) =>
(a['name'] ?? '').compareTo(b['name'] ?? ''));
break;
}
setState(() {
_filteredHistoryList = sortedList;
});
}
// === 清空历史记录 ===
Future<void> _clearHistory() async {
final confirmed = await _showConfirmDialog(
'清空历史记录',
'确定要清空所有历史记录吗?此操作不可撤销。',
);
if (confirmed == null || !confirmed) return;
try {
final success = await HistoryController.clearHistory();
if (success) {
setState(() {
_historyList.clear();
_filteredHistoryList.clear();
});
_showSnackBar('历史记录已清空');
} else {
_showSnackBar('清空失败');
}
} catch (e) {
print('清空历史记录失败: $e');
_showSnackBar('清空失败');
}
}
// === 删除单条记录 ===
Future<void> _deleteHistoryItem(int index, Map<String, dynamic> item) async {
final confirmed = await _showConfirmDialog(
'删除记录',
'确定要删除这条历史记录吗?',
);
if (confirmed == null || !confirmed) return;
try {
final poetryId = item['id'] as int;
final success = await HistoryController.removeFromHistory(poetryId);
if (success) {
// 重新加载历史记录,避免索引不匹配问题
await _loadHistory();
_showSnackBar('删除成功');
} else {
_showSnackBar('删除失败');
}
} catch (e) {
print('删除历史记录失败: $e');
_showSnackBar('删除失败');
}
}
// === 导出历史记录 ===
Future<void> _exportHistory() async {
try {
final exportData = await HistoryController.exportHistory(format: 'json');
if (exportData.isNotEmpty) {
// 这里可以实现文件保存功能
_showSnackBar('导出功能开发中...');
} else {
_showSnackBar('无数据可导出');
}
} catch (e) {
print('导出历史记录失败: $e');
_showSnackBar('导出失败');
}
}
// === 显示确认对话框 ===
Future<bool?> _showConfirmDialog(String title, String content) async {
return await showDialog<bool?>(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(content),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('确定'),
),
],
),
);
}
// === 显示历史记录统计 ===
void _showStatsDialog() async {
try {
final stats = await HistoryController.getHistoryStats();
if (stats.isEmpty) {
_showSnackBar('暂无统计数据');
return;
}
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('历史记录统计'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('总数: ${stats['totalCount']}'),
Text('今日: ${stats['todayCount']}'),
Text('本周: ${stats['thisWeekCount']}'),
Text('本月: ${stats['thisMonthCount']}'),
const SizedBox(height: 16),
const Text('热门朝代:'),
if (stats['topDynasties'] != null && stats['topDynasties'] is Map) ...[
...(stats['topDynasties'] as Map<String, int>).entries
.map((entry) => Text('${entry.key}: ${entry.value}'))
.toList(),
],
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('关闭'),
),
],
),
);
} catch (e) {
print('获取统计失败: $e');
_showSnackBar('获取统计失败');
}
}
void _showSnackBar(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: AppConstants.primaryColor,
duration: const Duration(seconds: 2),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
appBar: _buildAppBar(),
body: Column(
children: [
_buildSearchBar(),
_buildSortOptions(),
Expanded(
child: _isLoading
? _buildLoadingWidget()
: _filteredHistoryList.isEmpty
? _buildEmptyWidget()
: _buildHistoryList(),
),
],
),
);
}
// === 顶部导航栏 ===
PreferredSizeWidget _buildAppBar() {
return AppBar(
title: Text(
'历史记录',
style: TextStyle(
color: AppConstants.primaryColor,
fontWeight: FontWeight.bold,
),
),
backgroundColor: Colors.white,
elevation: 0,
centerTitle: true,
actions: [
IconButton(
icon: Icon(
Icons.delete_sweep,
color: AppConstants.primaryColor,
),
onPressed: _historyList.isEmpty ? null : _clearHistory,
),
IconButton(
icon: Icon(
Icons.bar_chart,
color: AppConstants.primaryColor,
),
onPressed: _showStatsDialog,
),
IconButton(
icon: Icon(
Icons.file_download,
color: AppConstants.primaryColor,
),
onPressed: _exportHistory,
),
],
);
}
// === 搜索栏 ===
Widget _buildSearchBar() {
return Container(
padding: const EdgeInsets.all(16),
child: TextField(
onChanged: _searchHistory,
decoration: InputDecoration(
hintText: '搜索历史记录...',
prefixIcon: const Icon(Icons.search),
suffixIcon: _searchKeyword.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
onPressed: () => _searchHistory(''),
)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: BorderSide(color: Colors.grey[300]!),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: BorderSide(color: AppConstants.primaryColor),
),
filled: true,
fillColor: Colors.white,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
),
),
);
}
// === 排序选项 ===
Widget _buildSortOptions() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
const Text('排序方式:'),
const SizedBox(width: 16),
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(20),
),
child: DropdownButton<String>(
value: _sortTypes[_selectedSortType],
isExpanded: true,
underline: Container(),
items: _sortTypes.map((type) {
return DropdownMenuItem<String>(
value: type,
child: Text(type),
);
}).toList(),
onChanged: (value) {
if (value != null) {
_sortHistory(_sortTypes.indexOf(value!));
}
},
),
),
),
],
),
);
}
// === 加载状态 ===
Widget _buildLoadingWidget() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text(
'加载历史记录...',
style: TextStyle(
fontSize: 16,
color: Colors.grey[600]!,
),
),
],
),
);
}
// === 空状态 ===
Widget _buildEmptyWidget() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.history,
size: 64,
color: Colors.grey[400]!,
),
SizedBox(height: 16),
Text(
'暂无历史记录',
style: TextStyle(
fontSize: 16,
color: Colors.grey[600]!,
),
),
SizedBox(height: 16),
ElevatedButton(
onPressed: () => Navigator.pop(context),
style: ElevatedButton.styleFrom(
backgroundColor: AppConstants.primaryColor,
),
child: Text('返回'),
),
],
),
);
}
// === 历史记录列表 ===
Widget _buildHistoryList() {
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _filteredHistoryList.length,
itemBuilder: (context, index) {
final item = _filteredHistoryList[index];
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: CircleAvatar(
radius: 20,
backgroundColor: AppConstants.primaryColor.withOpacity(0.1),
child: Text(
'${index + 1}',
style: TextStyle(
fontSize: 12,
color: AppConstants.primaryColor,
fontWeight: FontWeight.bold,
),
),
),
title: Text(
item['name'] ?? '未知诗词',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${item['alias'] ?? '未知朝代'}${item['date'] ?? ''}',
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
if (item['introduce']?.toString().isNotEmpty == true)
Text(
item['introduce']?.toString() ?? '',
style: TextStyle(
fontSize: 11,
color: Colors.grey[600]!,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
trailing: PopupMenuButton<String>(
onSelected: (value) {
switch (value) {
case 'delete':
_deleteHistoryItem(index, item);
break;
case 'share':
_showSnackBar('分享功能开发中...');
break;
case 'view':
_showSnackBar('查看详情功能开发中...');
break;
}
},
itemBuilder: (context) => [
const PopupMenuItem<String>(
value: 'delete',
child: Row(
children: [
Icon(Icons.delete, color: Colors.red),
SizedBox(width: 8),
Text('删除'),
],
),
),
const PopupMenuItem<String>(
value: 'share',
child: Row(
children: [
Icon(Icons.share, color: AppConstants.primaryColor),
SizedBox(width: 8),
Text('分享'),
],
),
),
const PopupMenuItem<String>(
value: 'view',
child: Row(
children: [
Icon(Icons.visibility, color: AppConstants.primaryColor),
SizedBox(width: 8),
Text('查看'),
],
),
),
],
),
onTap: () {
// 可以添加点击查看详情的功能
HapticFeedback.lightImpact();
},
),
);
},
);
}
}