Files
wushu/lib/controllers/history_controller.dart
Developer 888363785b 重构
2026-03-31 03:48:14 +08:00

658 lines
18 KiB
Dart
Raw Permalink 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 'shared_preferences_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 SharedPreferencesStorageController.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) {
return [];
}
}
/// 添加诗词到历史记录
/// [poetryData] 要保存的诗词数据
/// 如果诗词已存在,则不会重复添加
/// 返回是否添加成功
static Future<bool> addToHistory(Map<String, dynamic> poetryData) async {
if (_isAdding) {
return false;
}
_isAdding = true;
try {
final historyJson = await SharedPreferencesStorageController.getString(
_historyKey,
defaultValue: '[]',
);
final List<dynamic> historyList = json.decode(historyJson);
final existingIndex = historyList.indexWhere(
(item) => item['id'] == poetryData['id'],
);
if (existingIndex >= 0) {
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 SharedPreferencesStorageController.setString(
_historyKey,
updatedHistoryJson,
);
return true;
} catch (e) {
return false;
} finally {
_isAdding = false;
}
}
/// 从历史记录中移除指定诗词
/// [poetryId] 要移除的诗词ID
/// 返回是否移除成功
static Future<bool> removeFromHistory(int poetryId) async {
try {
final historyJson = await SharedPreferencesStorageController.getString(
_historyKey,
defaultValue: '[]',
);
final List<dynamic> historyList = json.decode(historyJson);
final originalLength = historyList.length;
historyList.removeWhere((item) => item['id'] == poetryId);
if (historyList.length < originalLength) {
final updatedHistoryJson = json.encode(historyList);
await SharedPreferencesStorageController.setString(
_historyKey,
updatedHistoryJson,
);
return true;
}
return false;
} catch (e) {
return false;
}
}
/// 清空所有历史记录
/// 返回是否清空成功
static Future<bool> clearHistory() async {
try {
await SharedPreferencesStorageController.remove(_historyKey);
return true;
} catch (e) {
return false;
}
}
static Future<int> getHistoryCount() async {
try {
final history = await getHistory();
return history.length;
} catch (e) {
return 0;
}
}
static Future<bool> isInHistory(int poetryId) async {
try {
final history = await getHistory();
return history.any((item) => item['id'] == poetryId);
} catch (e) {
return false;
}
}
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) {
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) {
return {};
}
}
static Future<String> exportHistory({String format = 'json'}) async {
try {
final history = await getHistory();
if (format.toLowerCase() == '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 {
return json.encode(history);
}
} catch (e) {
return '';
}
}
// ========== 点赞记录管理 ==========
/// 获取点赞诗词列表
/// 返回点赞的诗词列表
static Future<List<Map<String, dynamic>>> getLikedHistory() async {
try {
final likedJson = await SharedPreferencesStorageController.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) {
return [];
}
}
static Future<bool> addToLiked(Map<String, dynamic> poetryData) async {
try {
final likedJson = await SharedPreferencesStorageController.getString(
_likedKey,
defaultValue: '[]',
);
final List<dynamic> likedList = json.decode(likedJson);
final existingIndex = likedList.indexWhere(
(item) => item['id'] == poetryData['id'],
);
if (existingIndex >= 0) {
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 SharedPreferencesStorageController.setString(
_likedKey,
updatedLikedJson,
);
return true;
} catch (e) {
return false;
}
}
static Future<bool> removeLikedPoetry(String poetryId) async {
try {
final likedJson = await SharedPreferencesStorageController.getString(
_likedKey,
defaultValue: '[]',
);
final List<dynamic> likedList = json.decode(likedJson);
final originalLength = likedList.length;
likedList.removeWhere((item) => item['id'].toString() == poetryId);
if (likedList.length < originalLength) {
final updatedLikedJson = json.encode(likedList);
await SharedPreferencesStorageController.setString(
_likedKey,
updatedLikedJson,
);
return true;
}
return false;
} catch (e) {
return false;
}
}
static Future<bool> isInLiked(String poetryId) async {
try {
final likedList = await getLikedHistory();
return likedList.any((item) => item['id'].toString() == poetryId);
} catch (e) {
return false;
}
}
static Future<bool> clearLikedHistory() async {
try {
await SharedPreferencesStorageController.remove(_likedKey);
return true;
} catch (e) {
return false;
}
}
static Future<int> getLikedCount() async {
try {
final likedList = await getLikedHistory();
return likedList.length;
} catch (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 SharedPreferencesStorageController.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) {
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 SharedPreferencesStorageController.setString(_notesKey, notesJson);
return id;
} catch (e) {
return null;
}
}
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) {
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 SharedPreferencesStorageController.setString(_notesKey, notesJson);
return true;
} catch (e) {
return false;
}
}
static Future<bool> togglePinNote(String noteId) async {
try {
final notes = await getNotes();
final index = notes.indexWhere((n) => n['id'] == noteId);
if (index == -1) {
return false;
}
final currentPinned = notes[index]['isPinned'] ?? false;
notes[index]['isPinned'] = !currentPinned;
final notesJson = json.encode(notes);
await SharedPreferencesStorageController.setString(_notesKey, notesJson);
return !currentPinned;
} catch (e) {
return false;
}
}
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) {
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 SharedPreferencesStorageController.setString(_notesKey, notesJson);
return true;
} catch (e) {
return false;
}
}
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) {
return false;
}
final storedPassword = note['password'] as String?;
if (storedPassword == null || storedPassword.isEmpty) {
return true;
}
return storedPassword == password;
} catch (e) {
return false;
}
}
static Future<int> getNotesCount() async {
try {
final notes = await getNotes();
return notes.length;
} catch (e) {
return 0;
}
}
}