/// 自然写互动课堂平板端应用软件 V1.0 /// 护眼管理器 - 色温调节、使用时长监控、距离检测 /// /// 功能说明: /// 1. 色温调节(暖色滤镜,减少蓝光对眼睛的刺激) /// 2. 使用时长监控(按应用/科目统计,超时提醒休息) /// 3. 距离检测(前置摄像头检测用眼距离,过近时提醒) /// 4. 定时提醒(每30分钟提醒休息,远眺放松) /// 5. 家长远程管控(接收家长设置的时段/时长限制) /// 6. 护眼数据统计(每日使用时长报告) import 'dart:async'; /// 护眼模式配置 class EyeCareConfig { /// 是否启用护眼模式 bool enabled; /// 色温强度(0.0=关闭, 1.0=最暖) double colorTemperature; /// 连续使用提醒间隔(分钟) int reminderIntervalMinutes; /// 每日使用时长上限(分钟,0=不限制) int dailyLimitMinutes; /// 允许使用的时段(开始小时, 结束小时) int allowedStartHour; int allowedEndHour; /// 是否启用距离检测 bool distanceDetectionEnabled; /// 安全用眼距离(厘米) int safeDistanceCm; /// 夜间模式自动开启时间(小时) int nightModeStartHour; int nightModeEndHour; EyeCareConfig({ this.enabled = true, this.colorTemperature = 0.3, this.reminderIntervalMinutes = 30, this.dailyLimitMinutes = 120, this.allowedStartHour = 7, this.allowedEndHour = 21, this.distanceDetectionEnabled = false, this.safeDistanceCm = 30, this.nightModeStartHour = 20, this.nightModeEndHour = 7, }); Map toJson() => { 'enabled': enabled, 'color_temperature': colorTemperature, 'reminder_interval': reminderIntervalMinutes, 'daily_limit': dailyLimitMinutes, 'allowed_start': allowedStartHour, 'allowed_end': allowedEndHour, 'distance_enabled': distanceDetectionEnabled, 'safe_distance': safeDistanceCm, 'night_start': nightModeStartHour, 'night_end': nightModeEndHour, }; factory EyeCareConfig.fromJson(Map json) { return EyeCareConfig( enabled: json['enabled'] ?? true, colorTemperature: (json['color_temperature'] ?? 0.3).toDouble(), reminderIntervalMinutes: json['reminder_interval'] ?? 30, dailyLimitMinutes: json['daily_limit'] ?? 120, allowedStartHour: json['allowed_start'] ?? 7, allowedEndHour: json['allowed_end'] ?? 21, distanceDetectionEnabled: json['distance_enabled'] ?? false, safeDistanceCm: json['safe_distance'] ?? 30, nightModeStartHour: json['night_start'] ?? 20, nightModeEndHour: json['night_end'] ?? 7, ); } } /// 使用时长记录 class UsageRecord { final String date; // 日期 (yyyy-MM-dd) final String category; // 分类 (homework/practice/reading) final int durationMinutes; // 使用时长(分钟) final int sessionCount; // 使用次数 UsageRecord({ required this.date, required this.category, required this.durationMinutes, required this.sessionCount, }); Map toJson() => { 'date': date, 'category': category, 'duration': durationMinutes, 'sessions': sessionCount, }; } /// 护眼事件类型 enum EyeCareEvent { restReminder, // 休息提醒 dailyLimitReached, // 每日时长上限 outsideAllowedTime, // 超出允许使用时段 tooCloseWarning, // 用眼距离过近 nightModeOn, // 夜间模式开启 nightModeOff, // 夜间模式关闭 } /// 护眼事件回调 typedef EyeCareEventCallback = void Function(EyeCareEvent event, Map data); /// 护眼管理器 class EyeCareManager { /// 护眼配置 EyeCareConfig _config = EyeCareConfig(); /// 事件回调列表 final List _callbacks = []; /// 当前会话开始时间 DateTime? _sessionStartTime; /// 今日累计使用时长(秒) int _todayUsageSeconds = 0; /// 当前连续使用时长(秒) int _continuousUsageSeconds = 0; /// 今日使用记录 final Map _categoryUsage = {}; /// 计时器(每秒更新使用时长) Timer? _usageTimer; /// 距离检测计时器 Timer? _distanceTimer; /// 夜间模式检查计时器 Timer? _nightModeTimer; /// 当前是否在夜间模式 bool _isNightMode = false; /// 当前色温值(供外部读取) double get currentColorTemperature { if (!_config.enabled) return 0.0; if (_isNightMode) return _config.colorTemperature * 1.5; // 夜间加强 return _config.colorTemperature; } /// 今日总使用时长(分钟) int get todayUsageMinutes => _todayUsageSeconds ~/ 60; /// 剩余可用时长(分钟,-1表示不限制) int get remainingMinutes { if (_config.dailyLimitMinutes <= 0) return -1; return _config.dailyLimitMinutes - todayUsageMinutes; } /// 注册事件回调 void addCallback(EyeCareEventCallback callback) { _callbacks.add(callback); } /// 移除事件回调 void removeCallback(EyeCareEventCallback callback) { _callbacks.remove(callback); } /// 更新配置(家长远程设置后调用) void updateConfig(EyeCareConfig newConfig) { _config = newConfig; if (_config.enabled) { _startMonitoring(); } else { _stopMonitoring(); } } /// 开始使用(进入学习功能时调用) void startSession({String category = 'default'}) { _sessionStartTime = DateTime.now(); _continuousUsageSeconds = 0; // 检查是否在允许时段内 final now = DateTime.now(); if (_config.enabled && !_isWithinAllowedTime(now)) { _notifyEvent(EyeCareEvent.outsideAllowedTime, { 'allowed_start': _config.allowedStartHour, 'allowed_end': _config.allowedEndHour, }); } // 启动使用时长计时器 _usageTimer?.cancel(); _usageTimer = Timer.periodic(const Duration(seconds: 1), (_) { _todayUsageSeconds++; _continuousUsageSeconds++; // 检查连续使用时长提醒 if (_config.reminderIntervalMinutes > 0 && _continuousUsageSeconds > 0 && _continuousUsageSeconds % (_config.reminderIntervalMinutes * 60) == 0) { _notifyEvent(EyeCareEvent.restReminder, { 'continuous_minutes': _continuousUsageSeconds ~/ 60, 'total_minutes': todayUsageMinutes, }); } // 检查每日使用上限 if (_config.dailyLimitMinutes > 0 && todayUsageMinutes >= _config.dailyLimitMinutes) { _notifyEvent(EyeCareEvent.dailyLimitReached, { 'limit_minutes': _config.dailyLimitMinutes, 'used_minutes': todayUsageMinutes, }); } }); // 启动距离检测 if (_config.distanceDetectionEnabled) { _startDistanceDetection(); } // 启动夜间模式检查 _startNightModeCheck(); } /// 结束使用(退出学习功能时调用) void endSession({String category = 'default'}) { _usageTimer?.cancel(); _usageTimer = null; if (_sessionStartTime != null) { final duration = DateTime.now().difference(_sessionStartTime!).inMinutes; _categoryUsage[category] = (_categoryUsage[category] ?? 0) + duration; } _sessionStartTime = null; _continuousUsageSeconds = 0; _distanceTimer?.cancel(); _distanceTimer = null; } /// 用户休息后重置连续使用计时 void acknowledgeRest() { _continuousUsageSeconds = 0; } /// 检查当前时间是否在允许使用时段内 bool _isWithinAllowedTime(DateTime time) { final hour = time.hour; if (_config.allowedStartHour <= _config.allowedEndHour) { return hour >= _config.allowedStartHour && hour < _config.allowedEndHour; } else { // 跨午夜的情况 return hour >= _config.allowedStartHour || hour < _config.allowedEndHour; } } /// 启动监控 void _startMonitoring() { _startNightModeCheck(); } /// 停止监控 void _stopMonitoring() { _usageTimer?.cancel(); _distanceTimer?.cancel(); _nightModeTimer?.cancel(); } /// 启动距离检测(通过前置摄像头估算用眼距离) void _startDistanceDetection() { _distanceTimer?.cancel(); _distanceTimer = Timer.periodic(const Duration(seconds: 10), (_) { // 调用前置摄像头进行人脸检测 // 通过人脸框大小估算距离(人脸越大=距离越近) _checkEyeDistance(); }); } /// 检查用眼距离(基于前置摄像头人脸检测) void _checkEyeDistance() { // 实际实现: // 1. 使用CameraController获取前置摄像头预览帧 // 2. 使用MLKit/TFLite进行人脸检测 // 3. 根据人脸框宽度估算距离: distance = (focal_length * real_face_width) / face_width_in_pixels // 4. 本地处理,不上传图像数据(隐私保护) // 模拟距离检测结果 final estimatedDistanceCm = 35; // 实际从摄像头计算 if (estimatedDistanceCm < _config.safeDistanceCm) { _notifyEvent(EyeCareEvent.tooCloseWarning, { 'current_distance': estimatedDistanceCm, 'safe_distance': _config.safeDistanceCm, }); } } /// 启动夜间模式检查 void _startNightModeCheck() { _nightModeTimer?.cancel(); _nightModeTimer = Timer.periodic(const Duration(minutes: 1), (_) { final hour = DateTime.now().hour; final shouldBeNightMode = _isNightTimeHour(hour); if (shouldBeNightMode && !_isNightMode) { _isNightMode = true; _notifyEvent(EyeCareEvent.nightModeOn, {}); } else if (!shouldBeNightMode && _isNightMode) { _isNightMode = false; _notifyEvent(EyeCareEvent.nightModeOff, {}); } }); // 立即检查一次 final hour = DateTime.now().hour; _isNightMode = _isNightTimeHour(hour); } /// 判断是否为夜间时段 bool _isNightTimeHour(int hour) { if (_config.nightModeStartHour <= _config.nightModeEndHour) { return hour >= _config.nightModeStartHour && hour < _config.nightModeEndHour; } else { return hour >= _config.nightModeStartHour || hour < _config.nightModeEndHour; } } /// 获取今日使用统计 List getTodayUsageRecords() { final today = DateTime.now().toString().substring(0, 10); return _categoryUsage.entries.map((e) => UsageRecord( date: today, category: e.key, durationMinutes: e.value, sessionCount: 1, )).toList(); } /// 通知事件到所有回调 void _notifyEvent(EyeCareEvent event, Map data) { for (final callback in _callbacks) { try { callback(event, data); } catch (e) { // 忽略回调异常 } } } /// 释放资源 void dispose() { _usageTimer?.cancel(); _distanceTimer?.cancel(); _nightModeTimer?.cancel(); _callbacks.clear(); } }