software copyright
This commit is contained in:
@@ -0,0 +1,367 @@
|
||||
/// 自然写互动课堂平板端应用软件 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> toJson() => {
|
||||
'date': date, 'category': category,
|
||||
'duration': durationMinutes, 'sessions': sessionCount,
|
||||
};
|
||||
}
|
||||
|
||||
/// 护眼事件类型
|
||||
enum EyeCareEvent {
|
||||
restReminder, // 休息提醒
|
||||
dailyLimitReached, // 每日时长上限
|
||||
outsideAllowedTime, // 超出允许使用时段
|
||||
tooCloseWarning, // 用眼距离过近
|
||||
nightModeOn, // 夜间模式开启
|
||||
nightModeOff, // 夜间模式关闭
|
||||
}
|
||||
|
||||
/// 护眼事件回调
|
||||
typedef EyeCareEventCallback = void Function(EyeCareEvent event, Map<String, dynamic> data);
|
||||
|
||||
/// 护眼管理器
|
||||
class EyeCareManager {
|
||||
/// 护眼配置
|
||||
EyeCareConfig _config = EyeCareConfig();
|
||||
|
||||
/// 事件回调列表
|
||||
final List<EyeCareEventCallback> _callbacks = [];
|
||||
|
||||
/// 当前会话开始时间
|
||||
DateTime? _sessionStartTime;
|
||||
|
||||
/// 今日累计使用时长(秒)
|
||||
int _todayUsageSeconds = 0;
|
||||
|
||||
/// 当前连续使用时长(秒)
|
||||
int _continuousUsageSeconds = 0;
|
||||
|
||||
/// 今日使用记录
|
||||
final Map<String, int> _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<UsageRecord> 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<String, dynamic> data) {
|
||||
for (final callback in _callbacks) {
|
||||
try {
|
||||
callback(event, data);
|
||||
} catch (e) {
|
||||
// 忽略回调异常
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 释放资源
|
||||
void dispose() {
|
||||
_usageTimer?.cancel();
|
||||
_distanceTimer?.cancel();
|
||||
_nightModeTimer?.cancel();
|
||||
_callbacks.clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user