first commit
This commit is contained in:
199
lib/services/statistics_service.dart
Normal file
199
lib/services/statistics_service.dart
Normal file
@@ -0,0 +1,199 @@
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import '../models/daily_stats.dart';
|
||||
import '../models/app_usage.dart';
|
||||
import '../database/app_usage_dao.dart';
|
||||
import '../database/daily_stats_dao.dart';
|
||||
import 'mock_data_service.dart';
|
||||
|
||||
class StatisticsService {
|
||||
final AppUsageDao _appUsageDao = AppUsageDao();
|
||||
final DailyStatsDao _dailyStatsDao = DailyStatsDao();
|
||||
|
||||
// 获取今日统计(如果不存在则计算)
|
||||
Future<DailyStats> getTodayStats() async {
|
||||
// Web 平台返回测试数据
|
||||
if (kIsWeb) {
|
||||
return MockDataService.generateTodayStats();
|
||||
}
|
||||
|
||||
final today = DateTime.now();
|
||||
|
||||
// 先查数据库
|
||||
var stats = await _dailyStatsDao.getTodayStats();
|
||||
if (stats != null) {
|
||||
return stats;
|
||||
}
|
||||
|
||||
// 如果不存在,计算并保存
|
||||
stats = await _calculateDailyStats(today);
|
||||
if (stats != null) {
|
||||
await _dailyStatsDao.upsertDailyStats(stats);
|
||||
} else {
|
||||
// 如果没有数据,返回空统计
|
||||
stats = DailyStats(
|
||||
date: today,
|
||||
totalTime: 0,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
// 计算指定日期的统计
|
||||
Future<DailyStats?> _calculateDailyStats(DateTime date) async {
|
||||
final startOfDay = DateTime(date.year, date.month, date.day);
|
||||
final endOfDay = startOfDay.add(const Duration(days: 1));
|
||||
|
||||
// 获取该日期的所有应用使用记录
|
||||
final appUsages = await _appUsageDao.getAppUsages(
|
||||
startTime: startOfDay,
|
||||
endTime: endOfDay,
|
||||
);
|
||||
|
||||
if (appUsages.isEmpty) return null;
|
||||
|
||||
// 按分类聚合
|
||||
final categoryTime = <String, int>{};
|
||||
int totalTime = 0;
|
||||
int appSwitchCount = 0;
|
||||
|
||||
for (final usage in appUsages) {
|
||||
categoryTime[usage.category] = (categoryTime[usage.category] ?? 0) + usage.duration;
|
||||
totalTime += usage.duration;
|
||||
appSwitchCount += usage.deviceUnlockCount;
|
||||
}
|
||||
|
||||
// 计算效率评分
|
||||
final efficiencyScore = _calculateEfficiencyScore(categoryTime, totalTime);
|
||||
|
||||
// 计算专注度评分
|
||||
final focusScore = _calculateFocusScore(appSwitchCount, totalTime);
|
||||
|
||||
return DailyStats(
|
||||
date: startOfDay,
|
||||
totalTime: totalTime,
|
||||
workTime: categoryTime['work'] ?? 0,
|
||||
studyTime: categoryTime['study'] ?? 0,
|
||||
entertainmentTime: categoryTime['entertainment'] ?? 0,
|
||||
socialTime: categoryTime['social'] ?? 0,
|
||||
toolTime: categoryTime['tool'] ?? 0,
|
||||
efficiencyScore: efficiencyScore,
|
||||
focusScore: focusScore,
|
||||
appSwitchCount: appSwitchCount,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
// 计算效率评分
|
||||
int _calculateEfficiencyScore(Map<String, int> categoryTime, int totalTime) {
|
||||
if (totalTime == 0) return 0;
|
||||
|
||||
// 工作时间占比(40%)
|
||||
final workRatio = (categoryTime['work'] ?? 0) / totalTime;
|
||||
final workScore = workRatio * 40;
|
||||
|
||||
// 学习时间占比(30%)
|
||||
final studyRatio = (categoryTime['study'] ?? 0) / totalTime;
|
||||
final studyScore = studyRatio * 30;
|
||||
|
||||
// 娱乐时间占比(越低越好,30%)
|
||||
final entertainmentRatio = (categoryTime['entertainment'] ?? 0) / totalTime;
|
||||
final entertainmentScore = (1 - entertainmentRatio) * 30;
|
||||
|
||||
return (workScore + studyScore + entertainmentScore).round();
|
||||
}
|
||||
|
||||
// 计算专注度评分
|
||||
int _calculateFocusScore(int appSwitchCount, int totalTime) {
|
||||
if (totalTime == 0) return 0;
|
||||
|
||||
// 平均每小时切换次数
|
||||
final switchesPerHour = (appSwitchCount / (totalTime / 3600));
|
||||
|
||||
// 理想情况:每小时切换 < 10 次 = 100分
|
||||
// 每小时切换 > 50 次 = 0分
|
||||
final score = 100 - (switchesPerHour * 2).clamp(0, 100);
|
||||
|
||||
return score.round();
|
||||
}
|
||||
|
||||
// 获取本周统计
|
||||
Future<List<DailyStats>> getWeekStats() async {
|
||||
// Web 平台返回测试数据
|
||||
if (kIsWeb) {
|
||||
return MockDataService.generateWeekStats();
|
||||
}
|
||||
|
||||
final stats = await _dailyStatsDao.getWeekStats();
|
||||
|
||||
// 如果数据不完整,计算缺失的日期
|
||||
final now = DateTime.now();
|
||||
final startOfWeek = now.subtract(Duration(days: now.weekday - 1));
|
||||
|
||||
final result = <DailyStats>[];
|
||||
for (int i = 0; i < 7; i++) {
|
||||
final date = startOfWeek.add(Duration(days: i));
|
||||
final existing = stats.firstWhere(
|
||||
(s) => s.date.year == date.year &&
|
||||
s.date.month == date.month &&
|
||||
s.date.day == date.day,
|
||||
orElse: () => DailyStats(
|
||||
date: date,
|
||||
totalTime: 0,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
),
|
||||
);
|
||||
result.add(existing);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 获取本月统计
|
||||
Future<List<DailyStats>> getMonthStats() async {
|
||||
return await _dailyStatsDao.getMonthStats();
|
||||
}
|
||||
|
||||
// 刷新今日统计(重新计算)
|
||||
Future<DailyStats> refreshTodayStats() async {
|
||||
final today = DateTime.now();
|
||||
final stats = await _calculateDailyStats(today);
|
||||
|
||||
if (stats != null) {
|
||||
await _dailyStatsDao.upsertDailyStats(stats);
|
||||
return stats;
|
||||
} else {
|
||||
final emptyStats = DailyStats(
|
||||
date: today,
|
||||
totalTime: 0,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
await _dailyStatsDao.upsertDailyStats(emptyStats);
|
||||
return emptyStats;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取 Top 应用
|
||||
Future<List<AppUsage>> getTopApps({
|
||||
required DateTime startTime,
|
||||
required DateTime endTime,
|
||||
int limit = 10,
|
||||
}) async {
|
||||
// Web 平台返回测试数据
|
||||
if (kIsWeb) {
|
||||
return MockDataService.generateTopApps().take(limit).toList();
|
||||
}
|
||||
|
||||
return await _appUsageDao.getTopApps(
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
limit: limit,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user