Files
wushu/lib/controllers/history_controller.dart
2026-03-30 02:35:31 +08:00

784 lines
23 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.
/// 时间: 2025-03-23
/// 功能: 历史记录控制器
/// 介绍: 管理诗词历史记录的读取、写入、删除等操作
/// 最新变化: 添加笔记管理功能
import 'dart:convert';
import 'sqlite_storage_controller.dart';
/// 历史记录控制器类
/// 负责管理诗词浏览历史记录和点赞记录的本地存储和读取
class HistoryController {
static const String _historyKey = 'poetry_history';
static const String _likedKey = 'liked_poetry';
static const String _notesKey = 'user_notes';
static const int _maxHistoryCount = 100;
static bool _isAdding = false; // 防止并发添加
/// 获取历史记录列表
/// 返回按时间倒序排列的诗词历史记录
static Future<List<Map<String, dynamic>>> getHistory() async {
try {
final historyJson = await SQLiteStorageController.getString(
_historyKey,
defaultValue: '[]',
);
if (historyJson.isEmpty) {
return [];
}
final List<dynamic> historyList = json.decode(historyJson);
return historyList
.map((item) => Map<String, dynamic>.from(item))
.toList();
} catch (e) {
print('获取历史记录失败: $e');
return [];
}
}
/// 添加诗词到历史记录
/// [poetryData] 要保存的诗词数据
/// 如果诗词已存在,则不会重复添加
/// 返回是否添加成功
static Future<bool> addToHistory(Map<String, dynamic> poetryData) async {
// 防止并发调用
if (_isAdding) {
print('正在添加历史记录,跳过重复调用: ${poetryData['name']}');
return false;
}
_isAdding = true;
try {
print('开始添加历史记录: ${poetryData['name']} (ID: ${poetryData['id']})');
final historyJson = await SQLiteStorageController.getString(
_historyKey,
defaultValue: '[]',
);
final List<dynamic> historyList = json.decode(historyJson);
print('当前历史记录数量: ${historyList.length}');
// 检查是否已存在相同的诗词
final existingIndex = historyList.indexWhere(
(item) => item['id'] == poetryData['id'],
);
if (existingIndex >= 0) {
print('诗词已存在于历史记录中: ${poetryData['name']} (索引: $existingIndex)');
return false;
}
// 添加时间戳和日期
final enrichedPoetryData = Map<String, dynamic>.from(poetryData);
enrichedPoetryData['timestamp'] = DateTime.now().millisecondsSinceEpoch;
enrichedPoetryData['date'] = DateTime.now().toString().split(' ')[0];
// 插入到列表开头
historyList.insert(0, enrichedPoetryData);
// 保持最多指定数量的记录
if (historyList.length > _maxHistoryCount) {
historyList.removeRange(_maxHistoryCount, historyList.length);
}
// 保存到本地存储
final updatedHistoryJson = json.encode(historyList);
await SQLiteStorageController.setString(_historyKey, updatedHistoryJson);
print('已添加到历史记录: ${poetryData['name']} (新数量: ${historyList.length})');
return true;
} catch (e) {
print('添加历史记录失败: $e');
return false;
} finally {
_isAdding = false;
}
}
/// 从历史记录中移除指定诗词
/// [poetryId] 要移除的诗词ID
/// 返回是否移除成功
static Future<bool> removeFromHistory(int poetryId) async {
try {
print('开始删除历史记录: 诗词ID $poetryId');
final historyJson = await SQLiteStorageController.getString(
_historyKey,
defaultValue: '[]',
);
final List<dynamic> historyList = json.decode(historyJson);
final originalLength = historyList.length;
print('删除前历史记录数量: $originalLength');
// 查找匹配的记录
final matchingItems = <int>[];
for (int i = 0; i < historyList.length; i++) {
if (historyList[i]['id'] == poetryId) {
matchingItems.add(i);
}
}
print('找到匹配的记录索引: $matchingItems');
// 移除指定ID的诗词
historyList.removeWhere((item) => item['id'] == poetryId);
if (historyList.length < originalLength) {
// 保存更新后的列表
final updatedHistoryJson = json.encode(historyList);
await SQLiteStorageController.setString(
_historyKey,
updatedHistoryJson,
);
print(
'已从历史记录中移除诗词ID: $poetryId (删除了 ${originalLength - historyList.length} 条记录,剩余 ${historyList.length} 条)',
);
return true;
}
print('未找到要删除的诗词ID: $poetryId');
return false;
} catch (e) {
print('移除历史记录失败: $e');
return false;
}
}
/// 清空所有历史记录
/// 返回是否清空成功
static Future<bool> clearHistory() async {
try {
await SQLiteStorageController.remove(_historyKey);
print('已清空所有历史记录');
return true;
} catch (e) {
print('清空历史记录失败: $e');
return false;
}
}
/// 获取历史记录数量
/// 返回当前历史记录的总数
static Future<int> getHistoryCount() async {
try {
final history = await getHistory();
return history.length;
} catch (e) {
print('获取历史记录数量失败: $e');
return 0;
}
}
/// 检查诗词是否在历史记录中
/// [poetryId] 要检查的诗词ID
/// 返回是否存在
static Future<bool> isInHistory(int poetryId) async {
try {
final history = await getHistory();
return history.any((item) => item['id'] == poetryId);
} catch (e) {
print('检查历史记录失败: $e');
return false;
}
}
/// 搜索历史记录
/// [keyword] 搜索关键词
/// 返回匹配的历史记录列表
static Future<List<Map<String, dynamic>>> searchHistory(
String keyword,
) async {
try {
final history = await getHistory();
if (keyword.isEmpty) {
return history;
}
final lowerKeyword = keyword.toLowerCase();
return history.where((item) {
final name = (item['name'] ?? '').toString().toLowerCase();
final alias = (item['alias'] ?? '').toString().toLowerCase();
final introduce = (item['introduce'] ?? '').toString().toLowerCase();
return name.contains(lowerKeyword) ||
alias.contains(lowerKeyword) ||
introduce.contains(lowerKeyword);
}).toList();
} catch (e) {
print('搜索历史记录失败: $e');
return [];
}
}
/// 获取历史记录统计信息
/// 返回历史记录的统计数据
static Future<Map<String, dynamic>> getHistoryStats() async {
try {
final history = await getHistory();
if (history.isEmpty) {
return {
'totalCount': 0,
'todayCount': 0,
'thisWeekCount': 0,
'thisMonthCount': 0,
'topDynasties': <String, int>{},
};
}
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
final thisWeekStart = now.subtract(Duration(days: now.weekday - 1));
final thisMonthStart = DateTime(now.year, now.month, 1);
int todayCount = 0;
int thisWeekCount = 0;
int thisMonthCount = 0;
final Map<String, int> dynasties = {};
for (final item in history) {
final timestamp = item['timestamp'] as int?;
final date = DateTime.fromMillisecondsSinceEpoch(timestamp ?? 0);
// 统计今日
if (date.year == today.year &&
date.month == today.month &&
date.day == today.day) {
todayCount++;
}
// 统计本周
if (date.isAfter(thisWeekStart)) {
thisWeekCount++;
}
// 统计本月
if (date.isAfter(thisMonthStart)) {
thisMonthCount++;
}
// 统计朝代
final dynasty = item['alias']?.toString() ?? '未知';
dynasties[dynasty] = (dynasties[dynasty] ?? 0) + 1;
}
// 获取前5个最多朝代
final sortedDynasties = dynasties.entries.toList()
..sort((a, b) => b.value.compareTo(a.value))
..take(5);
return {
'totalCount': history.length,
'todayCount': todayCount,
'thisWeekCount': thisWeekCount,
'thisMonthCount': thisMonthCount,
'topDynasties': Map.fromEntries(sortedDynasties),
};
} catch (e) {
print('获取历史记录统计失败: $e');
return {};
}
}
/// 导出历史记录
/// [format] 导出格式 ('json' | 'csv')
/// 返回导出的字符串
static Future<String> exportHistory({String format = 'json'}) async {
try {
final history = await getHistory();
if (format.toLowerCase() == 'csv') {
// CSV格式导出
final buffer = StringBuffer();
buffer.writeln('ID,诗词名称,朝代,译文,原文,日期,时间戳');
for (final item in history) {
buffer.writeln(
'${item['id']},${item['name']},${item['alias']},${item['introduce']},${item['drtime']},${item['date']},${item['timestamp']}',
);
}
return buffer.toString();
} else {
// JSON格式导出
return json.encode(history);
}
} catch (e) {
print('导出历史记录失败: $e');
return '';
}
}
// ========== 点赞记录管理 ==========
/// 获取点赞诗词列表
/// 返回点赞的诗词列表
static Future<List<Map<String, dynamic>>> getLikedHistory() async {
try {
final likedJson = await SQLiteStorageController.getString(
_likedKey,
defaultValue: '[]',
);
if (likedJson.isEmpty) {
return [];
}
final List<dynamic> likedList = json.decode(likedJson);
return likedList.map((item) => Map<String, dynamic>.from(item)).toList();
} catch (e) {
print('获取点赞列表失败: $e');
return [];
}
}
/// 添加诗词到点赞列表
/// [poetryData] 要保存的诗词数据
/// 如果诗词已存在,则不会重复添加
/// 返回是否添加成功
static Future<bool> addToLiked(Map<String, dynamic> poetryData) async {
try {
print('开始添加点赞记录: ${poetryData['name']} (ID: ${poetryData['id']})');
final likedJson = await SQLiteStorageController.getString(
_likedKey,
defaultValue: '[]',
);
final List<dynamic> likedList = json.decode(likedJson);
print('当前点赞记录数量: ${likedList.length}');
// 检查是否已存在相同的诗词
final existingIndex = likedList.indexWhere(
(item) => item['id'] == poetryData['id'],
);
if (existingIndex >= 0) {
print('诗词已存在于点赞记录中: ${poetryData['name']} (索引: $existingIndex)');
return false;
}
// 添加时间戳和日期
final enrichedPoetryData = Map<String, dynamic>.from(poetryData);
final now = DateTime.now();
enrichedPoetryData['liked_timestamp'] = now.millisecondsSinceEpoch;
enrichedPoetryData['liked_date'] = now.toString().split(' ')[0];
enrichedPoetryData['liked_time'] =
'${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}';
// 插入到列表开头
likedList.insert(0, enrichedPoetryData);
// 保存到本地存储
final updatedLikedJson = json.encode(likedList);
await SQLiteStorageController.setString(_likedKey, updatedLikedJson);
print('已添加到点赞记录: ${poetryData['name']} (新数量: ${likedList.length})');
return true;
} catch (e) {
print('添加点赞记录失败: $e');
return false;
}
}
/// 从点赞列表中移除指定诗词
/// [poetryId] 要移除的诗词ID
/// 返回是否移除成功
static Future<bool> removeLikedPoetry(String poetryId) async {
try {
print('开始删除点赞记录: 诗词ID $poetryId');
final likedJson = await SQLiteStorageController.getString(
_likedKey,
defaultValue: '[]',
);
final List<dynamic> likedList = json.decode(likedJson);
final originalLength = likedList.length;
print('删除前点赞记录数量: $originalLength');
// 移除指定ID的诗词
likedList.removeWhere((item) => item['id'].toString() == poetryId);
if (likedList.length < originalLength) {
// 保存更新后的列表
final updatedLikedJson = json.encode(likedList);
await SQLiteStorageController.setString(_likedKey, updatedLikedJson);
print(
'已从点赞记录中移除诗词ID: $poetryId (删除了 ${originalLength - likedList.length} 条记录,剩余 ${likedList.length} 条)',
);
return true;
}
print('未找到要删除的诗词ID: $poetryId');
return false;
} catch (e) {
print('移除点赞记录失败: $e');
return false;
}
}
/// 检查诗词是否在点赞列表中
/// [poetryId] 要检查的诗词ID
/// 返回是否存在
static Future<bool> isInLiked(String poetryId) async {
try {
final likedList = await getLikedHistory();
return likedList.any((item) => item['id'].toString() == poetryId);
} catch (e) {
print('检查点赞记录失败: $e');
return false;
}
}
/// 清空所有点赞记录
/// 返回是否清空成功
static Future<bool> clearLikedHistory() async {
try {
await SQLiteStorageController.remove(_likedKey);
print('已清空所有点赞记录');
return true;
} catch (e) {
print('清空点赞记录失败: $e');
return false;
}
}
/// 获取点赞记录数量
/// 返回当前点赞记录的总数
static Future<int> getLikedCount() async {
try {
final likedList = await getLikedHistory();
return likedList.length;
} catch (e) {
print('获取点赞记录数量失败: $e');
return 0;
}
}
// ========== 笔记管理 ==========
/// 笔记数据结构说明:
/// - id: 笔记唯一标识(时间戳)
/// - title: 标题(可选)
/// - content: 内容
/// - time: 保存时间ISO8601格式
/// - createTime: 创建时间ISO8601格式
/// - charCount: 字数统计
/// - isPinned: 是否置顶true/false
/// - isLocked: 是否锁定true/false
/// - password: 访问密码(加密存储)
/// - category: 分类(可选)
/// 获取所有笔记列表
/// 返回笔记列表(置顶的排在前面)
static Future<List<Map<String, dynamic>>> getNotes() async {
try {
final notesJson = await SQLiteStorageController.getString(_notesKey);
if (notesJson.isEmpty) {
// 返回默认笔记示例
return _getDefaultNotes();
}
final List<dynamic> notesList = json.decode(notesJson);
final notes = notesList
.map((item) => Map<String, dynamic>.from(item))
.toList();
// 置顶的笔记排在前面
notes.sort((a, b) {
final aPinned = a['isPinned'] == true;
final bPinned = b['isPinned'] == true;
if (aPinned && !bPinned) return -1;
if (!aPinned && bPinned) return 1;
return 0;
});
return notes;
} catch (e) {
print('获取笔记列表失败: $e');
return _getDefaultNotes();
}
}
/// 获取默认笔记示例
static List<Map<String, dynamic>> _getDefaultNotes() {
final now = DateTime.now();
return [
{
'id': 'default_note_1',
'title': '欢迎使用笔记功能',
'content':
'这是一个示例笔记。\n\n你可以:\n• 创建新笔记\n• 编辑和删除笔记\n• 置顶重要笔记\n• 🔒 锁定笔记保护隐私\n• 选择分类整理笔记\n\n笔记会自动保存,无需手动操作。\n\n🔒 锁定功能说明:\n点击右上角锁图标可设置密码,设置后笔记将被锁定保护。\n\n🔐 体验锁定笔记:\n下方有一个默认锁定的笔记,密码为 qjsc\n点击即可体验锁定功能。',
'time': now.toIso8601String(),
'createTime': now.toIso8601String(),
'charCount': 100,
'isPinned': true,
'isLocked': false,
'password': null,
'category': '使用说明',
},
{
'id': 'default_note_2',
'title': '🔒 锁定笔记使用说明',
'content':
'这是一个锁定的笔记示例。\n\n使用方法:\n1. 点击右上角锁图标设置密码\n2. 设置密码后笔记会被锁定\n3. 在笔记列表中点击可进入编辑\n4. 再次点击锁图标可修改密码\n\n⚠️ 安全提示:\n• 笔记仅保存在本地设备\n• 不会上传到任何服务器\n• 与应用数据共存亡\n• 卸载应用后笔记将丢失\n• 请妥善保管您的密码\n\n🔐 当前密码qjsc',
'time': now.toIso8601String(),
'createTime': now.toIso8601String(),
'charCount': 120,
'isPinned': false,
'isLocked': true,
'password': 'qjsc',
'category': '安全说明',
},
];
}
/// 保存笔记(新建或更新)
/// [noteId] 笔记ID为null时新建
/// [title] 标题
/// [content] 内容
/// [isPinned] 是否置顶
/// [isLocked] 是否锁定
/// [password] 访问密码
/// [category] 分类
/// [createTime] 创建时间(可选,用于保留原创建时间)
/// 返回笔记ID
static Future<String?> saveNote({
String? noteId,
required String title,
required String content,
bool? isPinned,
bool? isLocked,
String? password,
String? category,
String? createTime,
}) async {
try {
final notes = await getNotes();
final now = DateTime.now();
final id = noteId ?? now.millisecondsSinceEpoch.toString();
// 如果是更新,保留原有的状态
bool pinned = isPinned ?? false;
bool locked = isLocked ?? false;
String? pwd = password;
String? cat = category;
String? ct = createTime;
if (noteId != null) {
final existingNote = notes.firstWhere(
(n) => n['id'] == noteId,
orElse: () => <String, dynamic>{},
);
if (isPinned == null) {
pinned = existingNote['isPinned'] ?? false;
}
if (isLocked == null) {
locked = existingNote['isLocked'] ?? false;
}
if (password == null) {
pwd = existingNote['password'];
}
if (category == null) {
cat = existingNote['category'];
}
// 如果没有传入创建时间,使用原有的创建时间
if (ct == null) {
ct = existingNote['createTime'];
}
}
// 新建笔记时设置创建时间
if (ct == null) {
ct = now.toIso8601String();
}
final noteData = {
'id': id,
'title': title,
'content': content,
'time': now.toIso8601String(),
'createTime': ct,
'charCount': title.length + content.length,
'isPinned': pinned,
'isLocked': locked,
'password': pwd,
'category': cat,
};
if (noteId != null) {
final index = notes.indexWhere((n) => n['id'] == noteId);
if (index != -1) {
notes[index] = noteData;
} else {
notes.insert(0, noteData);
}
} else {
notes.insert(0, noteData);
}
final notesJson = json.encode(notes);
await SQLiteStorageController.setString(_notesKey, notesJson);
print('保存笔记成功: $id, 置顶: $pinned, 锁定: $locked, 分类: $cat');
return id;
} catch (e) {
print('保存笔记失败: $e');
return null;
}
}
/// 获取单个笔记
/// [noteId] 笔记ID
static Future<Map<String, dynamic>?> getNote(String noteId) async {
try {
final notes = await getNotes();
return notes.firstWhere(
(n) => n['id'] == noteId,
orElse: () => <String, dynamic>{},
);
} catch (e) {
print('获取笔记失败: $e');
return null;
}
}
/// 删除笔记
/// [noteId] 笔记ID
static Future<bool> deleteNote(String noteId) async {
try {
final notes = await getNotes();
notes.removeWhere((n) => n['id'] == noteId);
final notesJson = json.encode(notes);
await SQLiteStorageController.setString(_notesKey, notesJson);
print('删除笔记成功: $noteId');
return true;
} catch (e) {
print('删除笔记失败: $e');
return false;
}
}
/// 切换笔记置顶状态
/// [noteId] 笔记ID
/// 返回新的置顶状态
static Future<bool> togglePinNote(String noteId) async {
try {
final notes = await getNotes();
final index = notes.indexWhere((n) => n['id'] == noteId);
if (index == -1) {
print('未找到笔记: $noteId');
return false;
}
final currentPinned = notes[index]['isPinned'] ?? false;
notes[index]['isPinned'] = !currentPinned;
final notesJson = json.encode(notes);
await SQLiteStorageController.setString(_notesKey, notesJson);
print('切换置顶状态: $noteId, 新状态: ${!currentPinned}');
return !currentPinned;
} catch (e) {
print('切换置顶状态失败: $e');
return false;
}
}
/// 设置笔记密码
/// [noteId] 笔记ID
/// [password] 密码(为空则取消锁定)
/// 返回是否成功
static Future<bool> setNotePassword(String noteId, String? password) async {
try {
final notes = await getNotes();
final index = notes.indexWhere((n) => n['id'] == noteId);
if (index == -1) {
print('未找到笔记: $noteId');
return false;
}
if (password == null || password.isEmpty) {
// 取消锁定
notes[index]['isLocked'] = false;
notes[index]['password'] = null;
} else {
// 设置密码并锁定
notes[index]['isLocked'] = true;
notes[index]['password'] = password;
}
final notesJson = json.encode(notes);
await SQLiteStorageController.setString(_notesKey, notesJson);
print(
'设置笔记密码成功: $noteId, 锁定: ${password != null && password.isNotEmpty}',
);
return true;
} catch (e) {
print('设置笔记密码失败: $e');
return false;
}
}
/// 验证笔记密码
/// [noteId] 笔记ID
/// [password] 输入的密码
/// 返回是否验证成功
static Future<bool> verifyNotePassword(String noteId, String password) async {
try {
final notes = await getNotes();
final note = notes.firstWhere(
(n) => n['id'] == noteId,
orElse: () => <String, dynamic>{},
);
if (note.isEmpty) {
print('未找到笔记: $noteId');
return false;
}
final storedPassword = note['password'] as String?;
if (storedPassword == null || storedPassword.isEmpty) {
// 没有密码,直接通过
return true;
}
return storedPassword == password;
} catch (e) {
print('验证笔记密码失败: $e');
return false;
}
}
/// 获取笔记数量
static Future<int> getNotesCount() async {
try {
final notes = await getNotes();
return notes.length;
} catch (e) {
print('获取笔记数量失败: $e');
return 0;
}
}
}