import 'package:get/get.dart'; import '../../../data/services/usage_stats_service.dart'; import '../../../data/models/translation_usage_stats.dart'; import '../../../core/utils/logger.dart'; import 'usage_controller.dart'; class TranslationStatsController extends GetxController { final UsageStatsService _usageService = Get.find(); final isLoading = false.obs; final selectedTabIndex = 0.obs; final selectedTimeRange = 'today'.tr.obs; // 今日 final Rx todayStats = TranslationUsageStats.empty('').obs; final Rx weekStats = TranslationUsageStats.empty('').obs; final Rx monthStats = TranslationUsageStats.empty('').obs; final RxList historyStats = [].obs; RealtimeUsageStats get realtimeStats => _usageService.realtimeTranslationStats.value; final RxMap costConfig = {}.obs; final RxDouble dailyCostThreshold = 10.0.obs; final RxDouble monthlyCostThreshold = 300.0.obs; final RxList> chartData = >[].obs; @override void onInit() { super.onInit(); _initializeData(); } void _initializeData() { ever(_usageService.todayTranslationStats, (stats) { todayStats.value = stats; _updateChartData(); }); loadAllStats(); } Future loadAllStats() async { isLoading.value = true; try { todayStats.value = _usageService.todayTranslationStats.value; await _loadWeekStats(); await _loadMonthStats(); await _loadHistoryStats(); _updateChartData(); Logger.info('统计数据加载完成'); } catch (e) { Logger.error('加载统计数据失败: $e'); Get.snackbar( 'loadFailed'.tr, // 加载失败 'statsLoadFailedTryLater'.tr, // 统计数据加载失败,请稍后重试 snackPosition: SnackPosition.TOP, backgroundColor: Get.theme.colorScheme.error.withValues(alpha: 0.1), colorText: Get.theme.colorScheme.error, ); } finally { isLoading.value = false; } } Future _loadWeekStats() async { try { final now = DateTime.now(); final weekStart = now.subtract(Duration(days: now.weekday - 1)); final weekEnd = weekStart.add(const Duration(days: 6)); final dailyStats = await _usageService.getHistoryStats( startDate: weekStart, endDate: weekEnd, ); weekStats.value = _aggregateStats(dailyStats, 'week'); } catch (e) { Logger.error('加载本周统计失败: $e'); } } Future _loadMonthStats() async { try { final now = DateTime.now(); monthStats.value = await _usageService.getMonthlyStats(now.year, now.month); } catch (e) { Logger.error('加载本月统计失败: $e'); } } Future _loadHistoryStats() async { try { final endDate = DateTime.now(); final startDate = endDate.subtract(const Duration(days: 7)); historyStats.value = await _usageService.getHistoryStats( startDate: startDate, endDate: endDate, ); historyStats.sort((a, b) => b.date.compareTo(a.date)); } catch (e) { Logger.error('加载历史统计失败: $e'); } } TranslationUsageStats _aggregateStats( List statsList, String period) { if (statsList.isEmpty) { return TranslationUsageStats.empty(_usageService.getCurrentUserId()); } int totalTranslationCalls = 0; int totalAsrCalls = 0; int totalTtsCalls = 0; int totalAiSummaryCalls = 0; int totalSourceChars = 0; int totalTargetChars = 0; int totalRecordingDuration = 0; int totalTtsPlayDuration = 0; int totalSessions = 0; final Map combinedLanguagePairUsage = {}; final Map combinedModeUsage = {}; for (final stats in statsList) { totalTranslationCalls += stats.translationApiCalls; totalAsrCalls += stats.asrApiCalls; totalTtsCalls += stats.ttsApiCalls; totalAiSummaryCalls += stats.aiSummaryApiCalls; totalSourceChars += stats.sourceTextCharacters; totalTargetChars += stats.ttsTextCharacters; totalRecordingDuration += stats.effectiveAudioDuration; totalTtsPlayDuration += stats.totalSessionDuration; totalSessions += stats.sessionCount; stats.languagePairUsage.forEach((key, value) { combinedLanguagePairUsage[key] = (combinedLanguagePairUsage[key] ?? 0) + value; }); stats.modeUsage.forEach((key, value) { combinedModeUsage[key] = (combinedModeUsage[key] ?? 0) + value; }); } return TranslationUsageStats( id: '${period}_${DateTime.now().millisecondsSinceEpoch}', date: DateTime.now(), userId: 'current_user', timeGranularity: period, translationApiCalls: totalTranslationCalls, asrApiCalls: totalAsrCalls, ttsApiCalls: totalTtsCalls, aiSummaryApiCalls: totalAiSummaryCalls, sourceTextCharacters: totalSourceChars, ttsTextCharacters: totalTargetChars, effectiveAudioDuration: totalRecordingDuration, totalSessionDuration: totalTtsPlayDuration, sessionCount: totalSessions, estimatedCost: 0.0, ); } void _updateChartData() { final List> data = []; switch (selectedTabIndex.value) { case 0: data.add({ 'label': 'today'.tr, // 今日 'apiCalls': todayStats.value.totalApiCalls, 'cost': 0.0, 'characters': todayStats.value.totalCharacterCount, }); break; case 1: data.add({ 'label': 'thisWeek'.tr, // 本周 'apiCalls': weekStats.value.totalApiCalls, 'cost': 0.0, 'characters': weekStats.value.totalCharacterCount, }); break; case 2: data.add({ 'label': 'thisMonth'.tr, // 本月 'apiCalls': monthStats.value.totalApiCalls, 'cost': 0.0, 'characters': monthStats.value.totalCharacterCount, }); break; case 3: for (final stats in historyStats.take(7)) { data.add({ 'label': '${stats.date.month}/${stats.date.day}', 'apiCalls': stats.totalApiCalls, 'cost': 0.0, 'characters': stats.totalCharacterCount, }); } break; } chartData.value = data; } void switchTimeRange(int index) { selectedTabIndex.value = index; switch (index) { case 0: selectedTimeRange.value = 'today'.tr; // 今日 break; case 1: selectedTimeRange.value = 'thisWeek'.tr; // 本周 break; case 2: selectedTimeRange.value = 'thisMonth'.tr; // 本月 break; case 3: selectedTimeRange.value = 'history'.tr; // 历史 break; } _updateChartData(); } TranslationUsageStats get currentStats { switch (selectedTabIndex.value) { case 0: return todayStats.value; case 1: return weekStats.value; case 2: return monthStats.value; default: return todayStats.value; } } Future refreshData() async { await loadAllStats(); } void cleanupOldData() { _usageService.cleanupOldData(); Get.snackbar( 'cleanupComplete'.tr, // 清理完成 'cleanedOldData'.tr, // 已清理90天前的过期数据 snackPosition: SnackPosition.TOP, backgroundColor: Get.theme.colorScheme.primary.withValues(alpha: 0.1), colorText: Get.theme.colorScheme.primary, ); } Future exportStats() async { try { Get.snackbar( 'exportFeature'.tr, // 导出功能 'exportInDevelopment'.tr, // 数据导出功能正在开发中 snackPosition: SnackPosition.TOP, backgroundColor: Get.theme.colorScheme.primary.withValues(alpha: 0.1), colorText: Get.theme.colorScheme.primary, ); } catch (e) { Logger.error('导出统计数据失败: $e'); } } String formatDuration(int seconds) { if (seconds < 60) { return '$seconds${'seconds'.tr}'; // 秒 } else if (seconds < 3600) { final minutes = seconds ~/ 60; final remainingSeconds = seconds % 60; return '$minutes${'minutes'.tr}$remainingSeconds${'seconds'.tr}'; // 分钟秒 } else { final hours = seconds ~/ 3600; final minutes = (seconds % 3600) ~/ 60; return '$hours${'hours'.tr}$minutes${'minutes'.tr}'; // 小时分钟 } } String formatCost(double cost) { return '¥${cost.toStringAsFixed(2)}'; } int _titleTapCount = 0; int _lastTitleTapTs = 0; void onTitleTap() { final now = DateTime.now().millisecondsSinceEpoch; if (now - _lastTitleTapTs <= 800) { _titleTapCount++; } else { _titleTapCount = 1; } _lastTitleTapTs = now; if (_titleTapCount >= 3) { _titleTapCount = 0; final uc = Get.find(); uc.toggleAiChat(!uc.showAiChat.value); } } String formatNumber(int number) { if (number < 1000) { return number.toString(); } else if (number < 1000000) { return '${(number / 1000).toStringAsFixed(1)}K'; } else { return '${(number / 1000000).toStringAsFixed(1)}M'; } } }