first commit

This commit is contained in:
ytc1012
2025-11-13 15:45:28 +08:00
commit 6b321890c0
54 changed files with 8412 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
import 'package:flutter/foundation.dart' show kIsWeb;
import '../models/app_usage.dart';
import 'database_helper.dart';
class AppUsageDao {
final DatabaseHelper _dbHelper = DatabaseHelper.instance;
// Web 平台检查
bool get _isWeb => kIsWeb;
// 插入应用使用记录
Future<int> insertAppUsage(AppUsage usage) async {
final db = await _dbHelper.database;
return await db.insert('app_usage', usage.toMap());
}
// 批量插入
Future<void> batchInsertAppUsages(List<AppUsage> usages) async {
final db = await _dbHelper.database;
final batch = db.batch();
for (final usage in usages) {
batch.insert('app_usage', usage.toMap());
}
await batch.commit(noResult: true);
}
// 获取指定时间范围的应用使用记录
Future<List<AppUsage>> getAppUsages({
required DateTime startTime,
required DateTime endTime,
}) async {
if (_isWeb) return [];
final db = await _dbHelper.database;
final startTimestamp = startTime.millisecondsSinceEpoch ~/ 1000;
final endTimestamp = endTime.millisecondsSinceEpoch ~/ 1000;
final maps = await db.query(
'app_usage',
where: 'start_time >= ? AND end_time <= ?',
whereArgs: [startTimestamp, endTimestamp],
orderBy: 'start_time DESC',
);
return maps.map((map) {
return AppUsage(
id: map['id'] as int?,
packageName: map['package_name'] as String,
appName: map['app_name'] as String,
startTime: DateTime.fromMillisecondsSinceEpoch((map['start_time'] as int) * 1000),
endTime: DateTime.fromMillisecondsSinceEpoch((map['end_time'] as int) * 1000),
duration: map['duration'] as int,
category: map['category'] as String,
projectId: map['project_id'] as int?,
deviceUnlockCount: map['device_unlock_count'] as int,
createdAt: DateTime.fromMillisecondsSinceEpoch((map['created_at'] as int) * 1000),
updatedAt: DateTime.fromMillisecondsSinceEpoch((map['updated_at'] as int) * 1000),
);
}).toList();
}
// 获取今日应用使用记录
Future<List<AppUsage>> getTodayAppUsages() async {
final now = DateTime.now();
final startOfDay = DateTime(now.year, now.month, now.day);
final endOfDay = startOfDay.add(const Duration(days: 1));
return await getAppUsages(startTime: startOfDay, endTime: endOfDay);
}
// 获取 Top N 应用
Future<List<AppUsage>> getTopApps({
required DateTime startTime,
required DateTime endTime,
int limit = 10,
}) async {
if (_isWeb) return [];
final db = await _dbHelper.database;
final startTimestamp = startTime.millisecondsSinceEpoch ~/ 1000;
final endTimestamp = endTime.millisecondsSinceEpoch ~/ 1000;
final maps = await db.rawQuery('''
SELECT
package_name,
app_name,
category,
SUM(duration) as total_duration,
MIN(start_time) as first_start_time,
MAX(end_time) as last_end_time,
MIN(created_at) as created_at,
MAX(updated_at) as updated_at
FROM app_usage
WHERE start_time >= ? AND end_time <= ?
GROUP BY package_name, app_name, category
ORDER BY total_duration DESC
LIMIT ?
''', [startTimestamp, endTimestamp, limit]);
return maps.map((map) {
final firstStart = DateTime.fromMillisecondsSinceEpoch((map['first_start_time'] as int) * 1000);
final lastEnd = DateTime.fromMillisecondsSinceEpoch((map['last_end_time'] as int) * 1000);
return AppUsage(
packageName: map['package_name'] as String,
appName: map['app_name'] as String,
startTime: firstStart,
endTime: lastEnd,
duration: map['total_duration'] as int,
category: map['category'] as String,
createdAt: DateTime.fromMillisecondsSinceEpoch((map['created_at'] as int) * 1000),
updatedAt: DateTime.fromMillisecondsSinceEpoch((map['updated_at'] as int) * 1000),
);
}).toList();
}
// 更新应用分类
Future<void> updateAppUsageCategory(int id, String category) async {
final db = await _dbHelper.database;
await db.update(
'app_usage',
{
'category': category,
'updated_at': DateTime.now().millisecondsSinceEpoch ~/ 1000,
},
where: 'id = ?',
whereArgs: [id],
);
}
// 删除应用使用记录
Future<void> deleteAppUsage(int id) async {
if (_isWeb) return;
final db = await _dbHelper.database;
await db.delete(
'app_usage',
where: 'id = ?',
whereArgs: [id],
);
}
// 删除指定日期之前的数据(用于数据清理)
Future<void> deleteBeforeDate(DateTime date) async {
if (_isWeb) return;
final db = await _dbHelper.database;
final timestamp = date.millisecondsSinceEpoch ~/ 1000;
await db.delete(
'app_usage',
where: 'start_time < ?',
whereArgs: [timestamp],
);
}
}

View File

@@ -0,0 +1,165 @@
import 'package:flutter/foundation.dart' show kIsWeb;
import '../models/daily_stats.dart';
import 'database_helper.dart';
class DailyStatsDao {
final DatabaseHelper _dbHelper = DatabaseHelper.instance;
// Web 平台检查
bool get _isWeb => kIsWeb;
// 插入或更新每日统计
Future<void> upsertDailyStats(DailyStats stats) async {
final db = await _dbHelper.database;
final dateStr = _formatDate(stats.date);
final map = stats.toMap();
final existing = await db.query(
'daily_stats',
where: 'date = ?',
whereArgs: [dateStr],
);
if (existing.isEmpty) {
await db.insert('daily_stats', map);
} else {
await db.update(
'daily_stats',
map,
where: 'date = ?',
whereArgs: [dateStr],
);
}
}
// 获取指定日期的统计
Future<DailyStats?> getDailyStats(DateTime date) async {
final db = await _dbHelper.database;
final dateStr = _formatDate(date);
final maps = await db.query(
'daily_stats',
where: 'date = ?',
whereArgs: [dateStr],
);
if (maps.isEmpty) return null;
return _mapToDailyStats(maps.first);
}
// 获取今日统计
Future<DailyStats?> getTodayStats() async {
return await getDailyStats(DateTime.now());
}
// 获取指定日期范围的统计
Future<List<DailyStats>> getStatsRange({
required DateTime startDate,
required DateTime endDate,
}) async {
if (_isWeb) return [];
final db = await _dbHelper.database;
final startStr = _formatDate(startDate);
final endStr = _formatDate(endDate);
final maps = await db.query(
'daily_stats',
where: 'date >= ? AND date <= ?',
whereArgs: [startStr, endStr],
orderBy: 'date ASC',
);
return maps.map((map) => _mapToDailyStats(map)).toList();
}
// 获取本周统计
Future<List<DailyStats>> getWeekStats() async {
final now = DateTime.now();
final startOfWeek = now.subtract(Duration(days: now.weekday - 1));
final endOfWeek = startOfWeek.add(const Duration(days: 6));
return await getStatsRange(startDate: startOfWeek, endDate: endOfWeek);
}
// 获取本月统计
Future<List<DailyStats>> getMonthStats() async {
final now = DateTime.now();
final startOfMonth = DateTime(now.year, now.month, 1);
final endOfMonth = DateTime(now.year, now.month + 1, 0);
return await getStatsRange(startDate: startOfMonth, endDate: endOfMonth);
}
// 删除指定日期之前的数据
Future<void> deleteBeforeDate(DateTime date) async {
if (_isWeb) return;
final db = await _dbHelper.database;
final dateStr = _formatDate(date);
await db.delete(
'daily_stats',
where: 'date < ?',
whereArgs: [dateStr],
);
}
// 删除指定 ID 的统计
Future<void> deleteDailyStats(int id) async {
if (_isWeb) return;
final db = await _dbHelper.database;
await db.delete(
'daily_stats',
where: 'id = ?',
whereArgs: [id],
);
}
String _formatDate(DateTime date) {
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
}
DailyStats _mapToDailyStats(Map<String, dynamic> map) {
return DailyStats(
id: map['id'] as int?,
date: DateTime.parse(map['date'] as String),
totalTime: map['total_time'] as int,
workTime: map['work_time'] as int? ?? 0,
studyTime: map['study_time'] as int? ?? 0,
entertainmentTime: map['entertainment_time'] as int? ?? 0,
socialTime: map['social_time'] as int? ?? 0,
toolTime: map['tool_time'] as int? ?? 0,
efficiencyScore: map['efficiency_score'] as int?,
focusScore: map['focus_score'] as int?,
appSwitchCount: map['app_switch_count'] as int? ?? 0,
createdAt: DateTime.fromMillisecondsSinceEpoch((map['created_at'] as int) * 1000),
updatedAt: DateTime.fromMillisecondsSinceEpoch((map['updated_at'] as int) * 1000),
);
}
}
// 扩展 DailyStats 模型,添加 toMap 方法
extension DailyStatsExtension on DailyStats {
Map<String, dynamic> toMap() {
return {
'id': id,
'date': _formatDate(date),
'total_time': totalTime,
'work_time': workTime,
'study_time': studyTime,
'entertainment_time': entertainmentTime,
'social_time': socialTime,
'tool_time': toolTime,
'efficiency_score': efficiencyScore,
'focus_score': focusScore,
'app_switch_count': appSwitchCount,
'created_at': createdAt.millisecondsSinceEpoch ~/ 1000,
'updated_at': updatedAt.millisecondsSinceEpoch ~/ 1000,
};
}
String _formatDate(DateTime date) {
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
}
}

View File

@@ -0,0 +1,114 @@
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class DatabaseHelper {
static final DatabaseHelper instance = DatabaseHelper._init();
static Database? _database;
DatabaseHelper._init();
Future<Database> get database async {
// Web 平台不支持 sqflite
if (kIsWeb) {
throw UnsupportedError('SQLite is not supported on Web platform. Use mock data instead.');
}
if (_database != null) return _database!;
_database = await _initDB('autotime_tracker.db');
return _database!;
}
Future<Database> _initDB(String filePath) async {
// Web 平台不支持 sqflite
if (kIsWeb) {
throw UnsupportedError('SQLite is not supported on Web platform.');
}
final dbPath = await getDatabasesPath();
final path = join(dbPath, filePath);
return await openDatabase(
path,
version: 1,
onCreate: _createDB,
);
}
Future<void> _createDB(Database db, int version) async {
// 应用使用记录表
await db.execute('''
CREATE TABLE app_usage (
id INTEGER PRIMARY KEY AUTOINCREMENT,
package_name TEXT NOT NULL,
app_name TEXT NOT NULL,
start_time INTEGER NOT NULL,
end_time INTEGER NOT NULL,
duration INTEGER NOT NULL,
category TEXT NOT NULL,
project_id INTEGER,
device_unlock_count INTEGER DEFAULT 0,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
''');
// 每日统计表
await db.execute('''
CREATE TABLE daily_stats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL UNIQUE,
total_time INTEGER NOT NULL,
work_time INTEGER DEFAULT 0,
study_time INTEGER DEFAULT 0,
entertainment_time INTEGER DEFAULT 0,
social_time INTEGER DEFAULT 0,
tool_time INTEGER DEFAULT 0,
efficiency_score INTEGER,
focus_score INTEGER,
app_switch_count INTEGER DEFAULT 0,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
''');
// 应用分类表
await db.execute('''
CREATE TABLE app_category (
id INTEGER PRIMARY KEY AUTOINCREMENT,
package_name TEXT NOT NULL UNIQUE,
category TEXT NOT NULL,
is_custom INTEGER DEFAULT 0,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
''');
// 时间目标表
await db.execute('''
CREATE TABLE time_goal (
id INTEGER PRIMARY KEY AUTOINCREMENT,
goal_type TEXT NOT NULL,
category TEXT,
target_time INTEGER NOT NULL,
is_active INTEGER DEFAULT 1,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
''');
// 创建索引
await db.execute('CREATE INDEX idx_app_usage_date ON app_usage(start_time)');
await db.execute('CREATE INDEX idx_app_usage_category ON app_usage(category)');
await db.execute('CREATE INDEX idx_app_usage_package ON app_usage(package_name)');
await db.execute('CREATE INDEX idx_daily_stats_date ON daily_stats(date)');
await db.execute('CREATE INDEX idx_app_category_package ON app_category(package_name)');
}
// 关闭数据库
Future<void> close() async {
final db = await database;
await db.close();
}
}

View File

@@ -0,0 +1,130 @@
import 'package:flutter/foundation.dart' show kIsWeb;
import '../models/time_goal.dart';
import 'database_helper.dart';
class TimeGoalDao {
final DatabaseHelper _dbHelper = DatabaseHelper.instance;
// Web 平台使用内存存储
final List<TimeGoal> _webGoals = [];
// Web 平台检查
bool get _isWeb => kIsWeb;
// 插入或更新时间目标
Future<void> upsertTimeGoal(TimeGoal goal) async {
if (_isWeb) {
// Web 平台使用内存存储
final index = _webGoals.indexWhere((g) =>
g.goalType == goal.goalType && g.category == goal.category);
if (index >= 0) {
_webGoals[index] = goal;
} else {
_webGoals.add(goal);
}
return;
}
final db = await _dbHelper.database;
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
final map = {
'goal_type': goal.goalType,
'category': goal.category,
'target_time': goal.targetTime,
'is_active': goal.isActive ? 1 : 0,
'created_at': now,
'updated_at': now,
};
// 检查是否已存在相同类型的目标
final existing = await db.query(
'time_goal',
where: 'goal_type = ? AND (category = ? OR category IS NULL)',
whereArgs: [
goal.goalType,
goal.category,
],
);
if (existing.isEmpty) {
await db.insert('time_goal', map);
} else {
await db.update(
'time_goal',
map,
where: 'goal_type = ? AND (category = ? OR category IS NULL)',
whereArgs: [
goal.goalType,
goal.category,
],
);
}
}
// 获取所有激活的目标
Future<List<TimeGoal>> getActiveGoals() async {
if (_isWeb) {
return _webGoals.where((g) => g.isActive).toList();
}
final db = await _dbHelper.database;
final maps = await db.query(
'time_goal',
where: 'is_active = 1',
orderBy: 'goal_type, category',
);
return maps.map((map) => TimeGoal.fromMap(map)).toList();
}
// 获取所有目标(包括非激活的)
Future<List<TimeGoal>> getAllGoals() async {
if (_isWeb) {
return List<TimeGoal>.from(_webGoals);
}
final db = await _dbHelper.database;
final maps = await db.query(
'time_goal',
orderBy: 'goal_type, category',
);
return maps.map((map) => TimeGoal.fromMap(map)).toList();
}
// 获取指定类型的目标
Future<TimeGoal?> getGoal(String goalType, {String? category}) async {
final db = await _dbHelper.database;
final maps = await db.query(
'time_goal',
where: 'goal_type = ? AND (category = ? OR category IS NULL) AND is_active = 1',
whereArgs: [goalType, category],
);
if (maps.isEmpty) return null;
return TimeGoal.fromMap(maps.first);
}
// 删除目标
Future<void> deleteGoal(int id) async {
final db = await _dbHelper.database;
await db.delete(
'time_goal',
where: 'id = ?',
whereArgs: [id],
);
}
// 停用目标
Future<void> deactivateGoal(int id) async {
final db = await _dbHelper.database;
await db.update(
'time_goal',
{'is_active': 0, 'updated_at': DateTime.now().millisecondsSinceEpoch ~/ 1000},
where: 'id = ?',
whereArgs: [id],
);
}
}