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,60 @@
import 'dart:async';
import 'time_tracking_service.dart';
import 'statistics_service.dart';
/// 后台同步服务 - 定期同步应用使用数据
class BackgroundSyncService {
final TimeTrackingService _timeTrackingService = TimeTrackingService();
final StatisticsService _statisticsService = StatisticsService();
Timer? _syncTimer;
bool _isRunning = false;
/// 启动后台同步
Future<void> start() async {
if (_isRunning) return;
_isRunning = true;
// 立即同步一次
await syncNow();
// 每 15 分钟同步一次
_syncTimer = Timer.periodic(const Duration(minutes: 15), (timer) async {
await syncNow();
});
// 启动原生后台追踪
await _timeTrackingService.startBackgroundTracking();
}
/// 停止后台同步
Future<void> stop() async {
_isRunning = false;
_syncTimer?.cancel();
_syncTimer = null;
await _timeTrackingService.stopBackgroundTracking();
}
/// 立即同步
Future<void> syncNow() async {
try {
print('Background sync: Starting sync...');
// 同步今日数据
await _timeTrackingService.syncTodayData();
// 刷新统计
await _statisticsService.refreshTodayStats();
print('Background sync: Completed');
} catch (e) {
print('Background sync error: $e');
}
}
/// 检查是否正在运行
bool get isRunning => _isRunning;
}

View File

@@ -0,0 +1,183 @@
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:sqflite/sqflite.dart';
import '../database/database_helper.dart';
class CategoryService {
final DatabaseHelper _dbHelper = DatabaseHelper.instance;
// Web 平台使用内存存储
final Map<String, String> _webCustomCategories = {};
// 预设分类规则
static const Map<String, String> defaultCategories = {
// 工作类
'com.microsoft.Office.Word': 'work',
'com.microsoft.Office.Excel': 'work',
'com.microsoft.Office.PowerPoint': 'work',
'com.slack': 'work',
'com.notion.Notion': 'work',
'com.figma.Figma': 'work',
'com.github': 'work',
'com.microsoft.VSCode': 'work',
'com.jetbrains': 'work',
'com.google.android.apps.docs': 'work',
'com.google.android.apps.sheets': 'work',
// 学习类
'com.coursera': 'study',
'com.khanacademy': 'study',
'com.amazon.kindle': 'study',
'com.gingerlabs.Notability': 'study',
'com.goodnotes': 'study',
'com.anki': 'study',
'com.duolingo': 'study',
// 娱乐类
'com.google.YouTube': 'entertainment',
'com.netflix.Netflix': 'entertainment',
'com.spotify.music': 'entertainment',
'com.spotify.client': 'entertainment',
'com.disney': 'entertainment',
// 社交类
'net.whatsapp.WhatsApp': 'social',
'com.instagram': 'social',
'com.twitter': 'social',
'com.facebook.Facebook': 'social',
'com.tencent.mm': 'social', // 微信
'com.tencent.mobileqq': 'social', // QQ
// 工具类
'com.apple.Safari': 'tool',
'com.android.chrome': 'tool',
'com.google.chrome': 'tool',
'com.microsoft.edge': 'tool',
'com.apple.mobilemail': 'tool',
'com.google.Gmail': 'tool',
};
// 获取应用分类
Future<String> getCategory(String packageName) async {
// 1. 先查用户自定义分类
final customCategory = await _getCustomCategory(packageName);
if (customCategory != null) {
return customCategory;
}
// 2. 查系统预设分类
if (defaultCategories.containsKey(packageName)) {
return defaultCategories[packageName]!;
}
// 3. 默认分类
return 'other';
}
// 获取用户自定义分类
Future<String?> _getCustomCategory(String packageName) async {
// Web 平台使用内存存储
if (kIsWeb) {
return _webCustomCategories[packageName];
}
final db = await _dbHelper.database;
final maps = await db.query(
'app_category',
where: 'package_name = ? AND is_custom = 1',
whereArgs: [packageName],
);
if (maps.isEmpty) return null;
return maps.first['category'] as String;
}
// 设置自定义分类
Future<void> setCategory(String packageName, String category) async {
// Web 平台使用内存存储
if (kIsWeb) {
_webCustomCategories[packageName] = category;
return;
}
final db = await _dbHelper.database;
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
await db.insert(
'app_category',
{
'package_name': packageName,
'category': category,
'is_custom': 1,
'created_at': now,
'updated_at': now,
},
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
// 删除自定义分类(恢复为系统默认)
Future<void> removeCustomCategory(String packageName) async {
// Web 平台使用内存存储
if (kIsWeb) {
_webCustomCategories.remove(packageName);
return;
}
final db = await _dbHelper.database;
await db.delete(
'app_category',
where: 'package_name = ? AND is_custom = 1',
whereArgs: [packageName],
);
}
// 获取所有自定义分类
Future<Map<String, String>> getAllCustomCategories() async {
// Web 平台使用内存存储
if (kIsWeb) {
return Map<String, String>.from(_webCustomCategories);
}
final db = await _dbHelper.database;
final maps = await db.query(
'app_category',
where: 'is_custom = 1',
);
final result = <String, String>{};
for (final map in maps) {
result[map['package_name'] as String] = map['category'] as String;
}
return result;
}
// 批量设置分类
Future<void> batchSetCategories(Map<String, String> categories) async {
// Web 平台使用内存存储
if (kIsWeb) {
_webCustomCategories.addAll(categories);
return;
}
final db = await _dbHelper.database;
final batch = db.batch();
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
for (final entry in categories.entries) {
batch.insert(
'app_category',
{
'package_name': entry.key,
'category': entry.value,
'is_custom': 1,
'created_at': now,
'updated_at': now,
},
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
await batch.commit(noResult: true);
}
}

View File

@@ -0,0 +1,156 @@
import '../database/app_usage_dao.dart';
import '../database/daily_stats_dao.dart';
import '../theme/app_theme.dart';
class ExportService {
final AppUsageDao _appUsageDao = AppUsageDao();
final DailyStatsDao _dailyStatsDao = DailyStatsDao();
/// 导出 CSV 格式数据
Future<String> exportToCSV({
required DateTime startDate,
required DateTime endDate,
}) async {
final appUsages = await _appUsageDao.getAppUsages(
startTime: startDate,
endTime: endDate,
);
final buffer = StringBuffer();
// CSV 头部
buffer.writeln('应用名称,包名,开始时间,结束时间,使用时长(秒),使用时长(格式化),分类');
// 数据行
for (final usage in appUsages) {
buffer.writeln([
_escapeCsvField(usage.appName),
_escapeCsvField(usage.packageName),
usage.startTime.toIso8601String(),
usage.endTime.toIso8601String(),
usage.duration.toString(),
usage.formattedDuration,
AppTheme.getCategoryName(usage.category),
].join(','));
}
return buffer.toString();
}
/// 导出统计报告(文本格式)
Future<String> exportStatsReport({
required DateTime startDate,
required DateTime endDate,
}) async {
final stats = await _dailyStatsDao.getStatsRange(
startDate: startDate,
endDate: endDate,
);
final buffer = StringBuffer();
buffer.writeln('=== AutoTime Tracker 统计报告 ===');
buffer.writeln('报告时间: ${startDate.toString().split(' ')[0]}${endDate.toString().split(' ')[0]}');
buffer.writeln('');
buffer.writeln('日期统计:');
buffer.writeln('');
int totalWorkTime = 0;
int totalStudyTime = 0;
int totalEntertainmentTime = 0;
int totalSocialTime = 0;
int totalToolTime = 0;
int totalTime = 0;
for (final stat in stats) {
buffer.writeln('${stat.date.toString().split(' ')[0]}:');
buffer.writeln(' 总时长: ${stat.formattedTotalTime}');
buffer.writeln(' 工作: ${_formatTime(stat.workTime)}');
buffer.writeln(' 学习: ${_formatTime(stat.studyTime)}');
buffer.writeln(' 娱乐: ${_formatTime(stat.entertainmentTime)}');
buffer.writeln(' 社交: ${_formatTime(stat.socialTime)}');
buffer.writeln(' 工具: ${_formatTime(stat.toolTime)}');
if (stat.efficiencyScore != null) {
buffer.writeln(' 效率评分: ${stat.efficiencyScore}%');
}
buffer.writeln('');
totalWorkTime += stat.workTime;
totalStudyTime += stat.studyTime;
totalEntertainmentTime += stat.entertainmentTime;
totalSocialTime += stat.socialTime;
totalToolTime += stat.toolTime;
totalTime += stat.totalTime;
}
buffer.writeln('总计:');
buffer.writeln(' 总时长: ${_formatTime(totalTime)}');
buffer.writeln(' 工作: ${_formatTime(totalWorkTime)} (${(totalWorkTime / totalTime * 100).toStringAsFixed(1)}%)');
buffer.writeln(' 学习: ${_formatTime(totalStudyTime)} (${(totalStudyTime / totalTime * 100).toStringAsFixed(1)}%)');
buffer.writeln(' 娱乐: ${_formatTime(totalEntertainmentTime)} (${(totalEntertainmentTime / totalTime * 100).toStringAsFixed(1)}%)');
buffer.writeln(' 社交: ${_formatTime(totalSocialTime)} (${(totalSocialTime / totalTime * 100).toStringAsFixed(1)}%)');
buffer.writeln(' 工具: ${_formatTime(totalToolTime)} (${(totalToolTime / totalTime * 100).toStringAsFixed(1)}%)');
return buffer.toString();
}
/// 导出今日报告
Future<String> exportTodayReport() async {
final today = DateTime.now();
final startOfDay = DateTime(today.year, today.month, today.day);
final endOfDay = startOfDay.add(const Duration(days: 1));
final stats = await _dailyStatsDao.getTodayStats();
final topApps = await _appUsageDao.getTopApps(
startTime: startOfDay,
endTime: endOfDay,
limit: 10,
);
final buffer = StringBuffer();
buffer.writeln('=== AutoTime Tracker 今日报告 ===');
buffer.writeln('日期: ${today.toString().split(' ')[0]}');
buffer.writeln('');
if (stats != null) {
buffer.writeln('总时长: ${stats.formattedTotalTime}');
buffer.writeln('工作: ${_formatTime(stats.workTime)}');
buffer.writeln('学习: ${_formatTime(stats.studyTime)}');
buffer.writeln('娱乐: ${_formatTime(stats.entertainmentTime)}');
buffer.writeln('社交: ${_formatTime(stats.socialTime)}');
buffer.writeln('工具: ${_formatTime(stats.toolTime)}');
if (stats.efficiencyScore != null) {
buffer.writeln('效率评分: ${stats.efficiencyScore}%');
}
buffer.writeln('');
}
if (topApps.isNotEmpty) {
buffer.writeln('Top 应用:');
for (int i = 0; i < topApps.length; i++) {
final app = topApps[i];
buffer.writeln('${i + 1}. ${app.appName}: ${app.formattedDuration} (${AppTheme.getCategoryName(app.category)})');
}
}
return buffer.toString();
}
String _escapeCsvField(String field) {
if (field.contains(',') || field.contains('"') || field.contains('\n')) {
return '"${field.replaceAll('"', '""')}"';
}
return field;
}
String _formatTime(int seconds) {
final hours = seconds ~/ 3600;
final minutes = (seconds % 3600) ~/ 60;
if (hours > 0) {
return '${hours}h ${minutes}m';
}
return '${minutes}m';
}
}

View File

@@ -0,0 +1,114 @@
import 'package:flutter/foundation.dart' show kIsWeb;
import '../models/daily_stats.dart';
import '../models/app_usage.dart';
/// 测试数据服务 - 用于 Web 平台或开发测试
class MockDataService {
/// 生成今日测试统计数据
static DailyStats generateTodayStats() {
return DailyStats(
date: DateTime.now(),
totalTime: 23040, // 6小时24分钟
workTime: 14400, // 4小时
studyTime: 3600, // 1小时
entertainmentTime: 3600, // 1小时
socialTime: 1800, // 30分钟
toolTime: 0,
efficiencyScore: 72,
focusScore: 65,
appSwitchCount: 45,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
}
/// 生成本周测试统计数据
static List<DailyStats> generateWeekStats() {
final now = DateTime.now();
final startOfWeek = now.subtract(Duration(days: now.weekday - 1));
return List.generate(7, (index) {
final date = startOfWeek.add(Duration(days: index));
final baseTime = 20000 + (index * 500); // 每天略有不同
return DailyStats(
date: date,
totalTime: baseTime,
workTime: (baseTime * 0.6).round(), // 60% 工作时间
studyTime: (baseTime * 0.15).round(), // 15% 学习时间
entertainmentTime: (baseTime * 0.15).round(), // 15% 娱乐时间
socialTime: (baseTime * 0.08).round(), // 8% 社交时间
toolTime: (baseTime * 0.02).round(), // 2% 工具时间
efficiencyScore: 65 + (index * 2), // 效率评分递增
focusScore: 60 + (index * 3),
appSwitchCount: 40 + (index * 2),
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
});
}
/// 生成今日 Top 应用测试数据
static List<AppUsage> generateTopApps() {
final now = DateTime.now();
return [
AppUsage(
packageName: 'com.google.chrome',
appName: 'Chrome',
startTime: now.subtract(const Duration(hours: 2, minutes: 15)),
endTime: now,
duration: 8100, // 2小时15分钟
category: 'work',
createdAt: now,
updatedAt: now,
),
AppUsage(
packageName: 'com.microsoft.vscode',
appName: 'VS Code',
startTime: now.subtract(const Duration(hours: 1, minutes: 30)),
endTime: now,
duration: 5400, // 1小时30分钟
category: 'work',
createdAt: now,
updatedAt: now,
),
AppUsage(
packageName: 'com.slack',
appName: 'Slack',
startTime: now.subtract(const Duration(hours: 1)),
endTime: now,
duration: 3600, // 1小时
category: 'work',
createdAt: now,
updatedAt: now,
),
AppUsage(
packageName: 'com.notion',
appName: 'Notion',
startTime: now.subtract(const Duration(minutes: 45)),
endTime: now,
duration: 2700, // 45分钟
category: 'work',
createdAt: now,
updatedAt: now,
),
AppUsage(
packageName: 'com.apple.mail',
appName: 'Mail',
startTime: now.subtract(const Duration(minutes: 30)),
endTime: now,
duration: 1800, // 30分钟
category: 'work',
createdAt: now,
updatedAt: now,
),
];
}
/// 检查是否应该使用测试数据
static bool shouldUseMockData() {
return kIsWeb;
}
}

View 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,
);
}
}

View File

@@ -0,0 +1,182 @@
import 'package:flutter/services.dart';
import '../models/app_usage.dart';
import '../database/app_usage_dao.dart';
import 'category_service.dart';
import 'statistics_service.dart';
class TimeTrackingService {
static const MethodChannel _channel = MethodChannel('autotime_tracker/time_tracking');
final AppUsageDao _appUsageDao = AppUsageDao();
final CategoryService _categoryService = CategoryService();
final StatisticsService _statisticsService = StatisticsService();
/// 检查权限状态
Future<bool> hasPermission() async {
try {
final result = await _channel.invokeMethod<bool>('hasPermission');
return result ?? false;
} catch (e) {
print('Error checking permission: $e');
return false;
}
}
/// 请求权限
Future<bool> requestPermission() async {
try {
final result = await _channel.invokeMethod<bool>('requestPermission');
return result ?? false;
} catch (e) {
print('Error requesting permission: $e');
return false;
}
}
/// 获取应用使用数据(从系统 API
Future<List<AppUsageData>> getAppUsageFromSystem({
required DateTime startTime,
required DateTime endTime,
}) async {
try {
final result = await _channel.invokeMethod<List<dynamic>>('getAppUsage', {
'startTime': startTime.millisecondsSinceEpoch,
'endTime': endTime.millisecondsSinceEpoch,
});
if (result == null) return [];
return result
.map((item) => AppUsageData.fromMap(Map<String, dynamic>.from(item)))
.toList();
} catch (e) {
print('Error getting app usage from system: $e');
return [];
}
}
/// 同步应用使用数据到数据库
Future<void> syncAppUsageToDatabase({
required DateTime startTime,
required DateTime endTime,
}) async {
try {
// 1. 从系统获取数据
final systemData = await getAppUsageFromSystem(
startTime: startTime,
endTime: endTime,
);
if (systemData.isEmpty) {
print('No app usage data from system');
return;
}
// 2. 转换为 AppUsage 模型并分类
final appUsages = <AppUsage>[];
for (final data in systemData) {
// 获取分类
final category = await _categoryService.getCategory(data.packageName);
// 创建 AppUsage 对象
final usage = AppUsage(
packageName: data.packageName,
appName: data.appName,
startTime: data.startTime,
endTime: data.endTime,
duration: data.duration,
category: category,
deviceUnlockCount: data.deviceUnlockCount ?? 0,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
appUsages.add(usage);
}
// 3. 批量插入数据库
if (appUsages.isNotEmpty) {
await _appUsageDao.batchInsertAppUsages(appUsages);
print('Synced ${appUsages.length} app usage records to database');
}
// 4. 更新今日统计
await _statisticsService.refreshTodayStats();
} catch (e) {
print('Error syncing app usage to database: $e');
rethrow;
}
}
/// 同步今日数据
Future<void> syncTodayData() async {
final now = DateTime.now();
final startOfDay = DateTime(now.year, now.month, now.day);
final endOfDay = startOfDay.add(const Duration(days: 1));
await syncAppUsageToDatabase(
startTime: startOfDay,
endTime: endOfDay,
);
}
/// 启动后台追踪
Future<void> startBackgroundTracking() async {
try {
await _channel.invokeMethod('startBackgroundTracking');
} catch (e) {
print('Error starting background tracking: $e');
}
}
/// 停止后台追踪
Future<void> stopBackgroundTracking() async {
try {
await _channel.invokeMethod('stopBackgroundTracking');
} catch (e) {
print('Error stopping background tracking: $e');
}
}
/// 检查后台追踪状态
Future<bool> isBackgroundTrackingActive() async {
try {
final result = await _channel.invokeMethod<bool>('isBackgroundTrackingActive');
return result ?? false;
} catch (e) {
print('Error checking background tracking status: $e');
return false;
}
}
}
/// 系统返回的应用使用数据模型
class AppUsageData {
final String packageName;
final String appName;
final DateTime startTime;
final DateTime endTime;
final int duration; // 秒
final int? deviceUnlockCount;
AppUsageData({
required this.packageName,
required this.appName,
required this.startTime,
required this.endTime,
required this.duration,
this.deviceUnlockCount,
});
factory AppUsageData.fromMap(Map<String, dynamic> map) {
return AppUsageData(
packageName: map['packageName'] as String,
appName: map['appName'] as String,
startTime: DateTime.fromMillisecondsSinceEpoch(map['startTime'] as int),
endTime: DateTime.fromMillisecondsSinceEpoch(map['endTime'] as int),
duration: map['duration'] as int,
deviceUnlockCount: map['deviceUnlockCount'] as int?,
);
}
}