You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
371 lines
12 KiB
371 lines
12 KiB
import 'dart:convert';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:get_storage/get_storage.dart';
|
|
import '../../../core/utils/logger.dart';
|
|
import '../models/translation_models.dart';
|
|
|
|
/// 翻译历史记录管理类
|
|
/// 负责管理翻译历史的存储、加载、分页等功能
|
|
class TranslationHistoryManager extends GetxController {
|
|
static const String TAG = 'TranslationHistoryManager';
|
|
|
|
// 存储相关配置
|
|
static const String _historyKey = 'translation_history';
|
|
static const int _maxStorageItems = 1000; // 存储限制
|
|
static const int _maxDisplayItems = 20; // 显示限制
|
|
|
|
final GetStorage _storage = GetStorage();
|
|
|
|
// 翻译历史数据
|
|
final translationHistory = <TranslationItem>[].obs;
|
|
|
|
// 分页加载相关状态
|
|
final isLoadingMore = false.obs;
|
|
final hasMoreHistory = true.obs;
|
|
final currentPage = 1.obs;
|
|
final pageSize = 20;
|
|
final isLoadingMoreError = false.obs;
|
|
bool _scrollListenerSetup = false;
|
|
|
|
// 滚动控制器
|
|
final ScrollController scrollController = ScrollController();
|
|
|
|
/// 初始化历史管理器
|
|
void initialize() {
|
|
loadTranslationHistory();
|
|
_setupScrollListener();
|
|
}
|
|
|
|
/// 加载翻译历史记录
|
|
/// 初始化时只加载最近的显示条数,提高性能
|
|
void loadTranslationHistory() {
|
|
final historyJson = _storage.read(_historyKey);
|
|
if (historyJson != null) {
|
|
try {
|
|
final List<dynamic> historyList = jsonDecode(historyJson);
|
|
final List<TranslationItem> allHistory =
|
|
historyList.map((item) => TranslationItem.fromJson(item)).toList();
|
|
|
|
// 初始化分页状态
|
|
currentPage.value = 1;
|
|
isLoadingMore.value = false;
|
|
isLoadingMoreError.value = false;
|
|
|
|
// 主页面只显示最近的记录,保证界面性能
|
|
if (allHistory.length > _maxDisplayItems) {
|
|
translationHistory.value =
|
|
allHistory.sublist(allHistory.length - _maxDisplayItems);
|
|
hasMoreHistory.value = true;
|
|
} else {
|
|
translationHistory.value = allHistory;
|
|
hasMoreHistory.value = false;
|
|
}
|
|
|
|
Logger.info('翻译历史加载成功: ${translationHistory.length}条记录');
|
|
} catch (e) {
|
|
_storage.remove(_historyKey);
|
|
Logger.error('加载翻译历史失败: ${e.toString()}');
|
|
}
|
|
} else {
|
|
// 没有历史数据时初始化分页状态
|
|
hasMoreHistory.value = false;
|
|
currentPage.value = 1;
|
|
isLoadingMore.value = false;
|
|
isLoadingMoreError.value = false;
|
|
}
|
|
}
|
|
|
|
/// 保存翻译历史记录
|
|
/// 只保存完成态记录,过滤中间态记录
|
|
void saveTranslationHistory() {
|
|
try {
|
|
// 只保存完成态记录
|
|
final List<TranslationItem> finalRecords =
|
|
translationHistory.where((item) => !item.isIntermediate).toList();
|
|
|
|
// 应用存储限制
|
|
List<TranslationItem> historyToSave = finalRecords;
|
|
if (finalRecords.length > _maxStorageItems) {
|
|
historyToSave =
|
|
finalRecords.sublist(finalRecords.length - _maxStorageItems);
|
|
}
|
|
|
|
// 保存到本地存储
|
|
final List<Map<String, dynamic>> historyJson =
|
|
historyToSave.map((item) => item.toJson()).toList();
|
|
_storage.write(_historyKey, jsonEncode(historyJson));
|
|
|
|
Logger.info('翻译历史保存成功: 存储${historyToSave.length}条记录');
|
|
} catch (e) {
|
|
Logger.error('保存翻译历史失败: ${e.toString()}');
|
|
}
|
|
}
|
|
|
|
/// 添加新的翻译记录
|
|
/// [item] 要添加的翻译记录
|
|
/// [scrollToBottom] 是否滚动到底部
|
|
void addTranslationItem(TranslationItem item, {bool scrollToBottom = true}) {
|
|
translationHistory.add(item);
|
|
if (scrollToBottom) {
|
|
_scrollToBottom();
|
|
}
|
|
}
|
|
|
|
/// 更新翻译记录
|
|
/// [timestamp] 记录的时间戳,用于定位记录
|
|
/// [updater] 更新函数
|
|
bool updateTranslationItem(
|
|
DateTime timestamp, Function(TranslationItem) updater) {
|
|
final index = translationHistory
|
|
.indexWhere((item) => item.timestamp.isAtSameMomentAs(timestamp));
|
|
if (index != -1) {
|
|
updater(translationHistory[index]);
|
|
translationHistory.refresh();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// 加载更多历史记录(分页加载)
|
|
Future<void> loadMoreHistory() async {
|
|
if (isLoadingMore.value || !hasMoreHistory.value) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
isLoadingMore.value = true;
|
|
isLoadingMoreError.value = false;
|
|
|
|
final nextPage = currentPage.value + 1;
|
|
Logger.i(TAG, '正在加载第 $nextPage 页翻译历史...');
|
|
|
|
// 从存储中获取所有历史记录
|
|
final historyJson = _storage.read(_historyKey);
|
|
if (historyJson != null) {
|
|
final List<dynamic> historyList = jsonDecode(historyJson);
|
|
final List<TranslationItem> allHistory =
|
|
historyList.map((item) => TranslationItem.fromJson(item)).toList();
|
|
|
|
// 计算分页参数
|
|
final totalItems = allHistory.length;
|
|
final endIndex = totalItems - ((nextPage - 1) * pageSize);
|
|
final startIndex = totalItems - (nextPage * pageSize);
|
|
|
|
if (endIndex > 0) {
|
|
final actualStartIndex = startIndex < 0 ? 0 : startIndex;
|
|
final actualEndIndex = endIndex > totalItems ? totalItems : endIndex;
|
|
|
|
final pageItems =
|
|
allHistory.sublist(actualStartIndex, actualEndIndex);
|
|
|
|
if (pageItems.isNotEmpty) {
|
|
await Future.delayed(const Duration(milliseconds: 300));
|
|
|
|
// reverse模式下:将新记录插入到现有记录列表的开头
|
|
final updatedHistory = [...pageItems, ...translationHistory];
|
|
translationHistory.assignAll(updatedHistory);
|
|
|
|
// 更新分页状态
|
|
currentPage.value = nextPage;
|
|
hasMoreHistory.value = startIndex > 0;
|
|
|
|
Logger.i(TAG, '成功加载 ${pageItems.length} 条历史记录');
|
|
} else {
|
|
hasMoreHistory.value = false;
|
|
}
|
|
} else {
|
|
hasMoreHistory.value = false;
|
|
}
|
|
} else {
|
|
hasMoreHistory.value = false;
|
|
}
|
|
} catch (e) {
|
|
Logger.e(TAG, '加载更多历史记录失败: $e');
|
|
isLoadingMoreError.value = true;
|
|
} finally {
|
|
isLoadingMore.value = false;
|
|
}
|
|
}
|
|
|
|
/// 获取所有完成态历史记录
|
|
List<TranslationItem> getAllHistory() {
|
|
try {
|
|
final List<TranslationItem> result = <TranslationItem>[];
|
|
final Set<String> existingIds = <String>{};
|
|
|
|
// 添加存储的历史记录
|
|
final historyJson = _storage.read(_historyKey);
|
|
if (historyJson != null) {
|
|
final List<dynamic> historyList = jsonDecode(historyJson);
|
|
final List<TranslationItem> storedHistory =
|
|
historyList.map((item) => TranslationItem.fromJson(item)).toList();
|
|
|
|
for (final item in storedHistory) {
|
|
final id =
|
|
'${item.sessionId}_${item.timestamp.millisecondsSinceEpoch}';
|
|
if (!existingIds.contains(id)) {
|
|
existingIds.add(id);
|
|
result.add(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 添加当前内存中的完成态记录
|
|
for (final item in translationHistory) {
|
|
if (!item.isIntermediate) {
|
|
final id =
|
|
'${item.sessionId}_${item.timestamp.millisecondsSinceEpoch}';
|
|
if (!existingIds.contains(id)) {
|
|
existingIds.add(id);
|
|
result.add(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 按时间排序
|
|
result.sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
|
return result;
|
|
} catch (e) {
|
|
Logger.error('获取完整历史记录失败: ${e.toString()}');
|
|
return translationHistory.where((item) => !item.isIntermediate).toList();
|
|
}
|
|
}
|
|
|
|
/// 清除所有翻译历史
|
|
void clearTranslationHistory() {
|
|
translationHistory.clear();
|
|
_storage.remove(_historyKey);
|
|
Logger.info('翻译历史已清除');
|
|
}
|
|
|
|
/// 删除指定会话的所有翻译记录
|
|
void deleteSessionHistory(String sessionId) {
|
|
try {
|
|
// 从内存中删除
|
|
final originalCount = translationHistory.length;
|
|
translationHistory.removeWhere((item) => item.sessionId == sessionId);
|
|
final removedFromMemory = originalCount - translationHistory.length;
|
|
|
|
// 从存储中删除
|
|
final historyJson = _storage.read(_historyKey);
|
|
int removedFromStorage = 0;
|
|
|
|
if (historyJson != null) {
|
|
final List<dynamic> historyList = jsonDecode(historyJson);
|
|
final List<TranslationItem> storedHistory =
|
|
historyList.map((item) => TranslationItem.fromJson(item)).toList();
|
|
|
|
final originalStoredCount = storedHistory.length;
|
|
storedHistory.removeWhere((item) => item.sessionId == sessionId);
|
|
removedFromStorage = originalStoredCount - storedHistory.length;
|
|
|
|
// 重新保存
|
|
final List<Map<String, dynamic>> updatedHistoryJson =
|
|
storedHistory.map((item) => item.toJson()).toList();
|
|
_storage.write(_historyKey, jsonEncode(updatedHistoryJson));
|
|
}
|
|
|
|
translationHistory.refresh();
|
|
|
|
Logger.info(
|
|
'删除会话历史成功: sessionId=$sessionId, 内存删除${removedFromMemory}条, 存储删除${removedFromStorage}条');
|
|
} catch (e) {
|
|
Logger.error('删除会话历史失败: ${e.toString()}');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// 删除指定的单条翻译记录
|
|
void deleteTranslationItem(String sessionId, DateTime timestamp) {
|
|
try {
|
|
// 从内存中删除
|
|
final originalCount = translationHistory.length;
|
|
translationHistory.removeWhere((item) =>
|
|
item.sessionId == sessionId &&
|
|
item.timestamp.millisecondsSinceEpoch ==
|
|
timestamp.millisecondsSinceEpoch);
|
|
final removedFromMemory = originalCount - translationHistory.length;
|
|
|
|
// 从存储中删除
|
|
final historyJson = _storage.read(_historyKey);
|
|
int removedFromStorage = 0;
|
|
|
|
if (historyJson != null) {
|
|
final List<dynamic> historyList = jsonDecode(historyJson);
|
|
final List<TranslationItem> storedHistory =
|
|
historyList.map((item) => TranslationItem.fromJson(item)).toList();
|
|
|
|
final originalStoredCount = storedHistory.length;
|
|
storedHistory.removeWhere((item) =>
|
|
item.sessionId == sessionId &&
|
|
item.timestamp.millisecondsSinceEpoch ==
|
|
timestamp.millisecondsSinceEpoch);
|
|
removedFromStorage = originalStoredCount - storedHistory.length;
|
|
|
|
// 重新保存
|
|
final List<Map<String, dynamic>> updatedHistoryJson =
|
|
storedHistory.map((item) => item.toJson()).toList();
|
|
_storage.write(_historyKey, jsonEncode(updatedHistoryJson));
|
|
}
|
|
|
|
translationHistory.refresh();
|
|
|
|
Logger.info(
|
|
'删除单条翻译记录成功: sessionId=$sessionId, 内存删除${removedFromMemory}条, 存储删除${removedFromStorage}条');
|
|
} catch (e) {
|
|
Logger.error('删除单条翻译记录失败: ${e.toString()}');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// 滚动到底部
|
|
void scrollToBottom({bool animate = true}) {
|
|
_scrollToBottom(animate: animate);
|
|
}
|
|
|
|
/// 设置滚动监听器
|
|
void _setupScrollListener() {
|
|
if (_scrollListenerSetup) return;
|
|
|
|
scrollController.addListener(() {
|
|
// reverse模式下:检查是否滚动到底部
|
|
if (scrollController.position.pixels >=
|
|
scrollController.position.maxScrollExtent - 100) {
|
|
if (hasMoreHistory.value && !isLoadingMore.value) {
|
|
Logger.d(TAG, '触发加载更多翻译历史记录');
|
|
loadMoreHistory();
|
|
}
|
|
}
|
|
});
|
|
|
|
_scrollListenerSetup = true;
|
|
}
|
|
|
|
/// 内部滚动到底部方法
|
|
void _scrollToBottom({bool animate = true}) {
|
|
if (scrollController.hasClients && translationHistory.isNotEmpty) {
|
|
try {
|
|
const targetPosition = 0.0; // reverse模式下最新消息在顶部
|
|
|
|
if (animate) {
|
|
scrollController.animateTo(
|
|
targetPosition,
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.easeOut,
|
|
);
|
|
} else {
|
|
scrollController.jumpTo(targetPosition);
|
|
}
|
|
} catch (e) {
|
|
Logger.error('滚动到顶部失败: ${e.toString()}');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 释放资源
|
|
void dispose() {
|
|
scrollController.dispose();
|
|
Logger.info('翻译历史管理器已释放');
|
|
}
|
|
}
|
|
|