/// 时间: 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>> getHistory() async { try { final historyJson = await SQLiteStorageController.getString( _historyKey, defaultValue: '[]', ); if (historyJson.isEmpty) { return []; } final List historyList = json.decode(historyJson); return historyList .map((item) => Map.from(item)) .toList(); } catch (e) { return []; } } /// 添加诗词到历史记录 /// [poetryData] 要保存的诗词数据 /// 如果诗词已存在,则不会重复添加 /// 返回是否添加成功 static Future addToHistory(Map poetryData) async { if (_isAdding) { return false; } _isAdding = true; try { final historyJson = await SQLiteStorageController.getString( _historyKey, defaultValue: '[]', ); final List historyList = json.decode(historyJson); final existingIndex = historyList.indexWhere( (item) => item['id'] == poetryData['id'], ); if (existingIndex >= 0) { return false; } final enrichedPoetryData = Map.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); return true; } catch (e) { return false; } finally { _isAdding = false; } } /// 从历史记录中移除指定诗词 /// [poetryId] 要移除的诗词ID /// 返回是否移除成功 static Future removeFromHistory(int poetryId) async { try { final historyJson = await SQLiteStorageController.getString( _historyKey, defaultValue: '[]', ); final List historyList = json.decode(historyJson); final originalLength = historyList.length; historyList.removeWhere((item) => item['id'] == poetryId); if (historyList.length < originalLength) { final updatedHistoryJson = json.encode(historyList); await SQLiteStorageController.setString( _historyKey, updatedHistoryJson, ); return true; } return false; } catch (e) { return false; } } /// 清空所有历史记录 /// 返回是否清空成功 static Future clearHistory() async { try { await SQLiteStorageController.remove(_historyKey); return true; } catch (e) { return false; } } static Future getHistoryCount() async { try { final history = await getHistory(); return history.length; } catch (e) { return 0; } } static Future isInHistory(int poetryId) async { try { final history = await getHistory(); return history.any((item) => item['id'] == poetryId); } catch (e) { return false; } } static Future>> 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> getHistoryStats() async { try { final history = await getHistory(); if (history.isEmpty) { return { 'totalCount': 0, 'todayCount': 0, 'thisWeekCount': 0, 'thisMonthCount': 0, 'topDynasties': {}, }; } 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 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 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>> getLikedHistory() async { try { final likedJson = await SQLiteStorageController.getString( _likedKey, defaultValue: '[]', ); if (likedJson.isEmpty) { return []; } final List likedList = json.decode(likedJson); return likedList.map((item) => Map.from(item)).toList(); } catch (e) { return []; } } static Future addToLiked(Map poetryData) async { try { final likedJson = await SQLiteStorageController.getString( _likedKey, defaultValue: '[]', ); final List likedList = json.decode(likedJson); final existingIndex = likedList.indexWhere( (item) => item['id'] == poetryData['id'], ); if (existingIndex >= 0) { return false; } final enrichedPoetryData = Map.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); return true; } catch (e) { return false; } } static Future removeLikedPoetry(String poetryId) async { try { final likedJson = await SQLiteStorageController.getString( _likedKey, defaultValue: '[]', ); final List 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 SQLiteStorageController.setString(_likedKey, updatedLikedJson); return true; } return false; } catch (e) { return false; } } static Future isInLiked(String poetryId) async { try { final likedList = await getLikedHistory(); return likedList.any((item) => item['id'].toString() == poetryId); } catch (e) { return false; } } static Future clearLikedHistory() async { try { await SQLiteStorageController.remove(_likedKey); return true; } catch (e) { return false; } } static Future 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>> getNotes() async { try { final notesJson = await SQLiteStorageController.getString(_notesKey); if (notesJson.isEmpty) { // 返回默认笔记示例 return _getDefaultNotes(); } final List notesList = json.decode(notesJson); final notes = notesList .map((item) => Map.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> _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 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: () => {}, ); 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); return id; } catch (e) { return null; } } static Future?> getNote(String noteId) async { try { final notes = await getNotes(); return notes.firstWhere( (n) => n['id'] == noteId, orElse: () => {}, ); } catch (e) { return null; } } /// 删除笔记 /// [noteId] 笔记ID static Future 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); return true; } catch (e) { return false; } } static Future 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 SQLiteStorageController.setString(_notesKey, notesJson); return !currentPinned; } catch (e) { return false; } } static Future 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 SQLiteStorageController.setString(_notesKey, notesJson); return true; } catch (e) { return false; } } static Future verifyNotePassword(String noteId, String password) async { try { final notes = await getNotes(); final note = notes.firstWhere( (n) => n['id'] == noteId, orElse: () => {}, ); 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 getNotesCount() async { try { final notes = await getNotes(); return notes.length; } catch (e) { return 0; } } }