247 lines
8.4 KiB
C#
247 lines
8.4 KiB
C#
using System;
|
||
using System.Windows.Forms;
|
||
using Microsoft.Win32;
|
||
|
||
namespace TimerApp
|
||
{
|
||
public enum MonitorState
|
||
{
|
||
Idle,
|
||
Working,
|
||
Resting
|
||
}
|
||
|
||
public sealed class ActivityMonitor : IDisposable
|
||
{
|
||
private readonly System.Windows.Forms.Timer _timer;
|
||
private TimeSpan _accumulatedWorkTime;
|
||
private int _restElapsedSeconds;
|
||
private bool _isPaused;
|
||
private bool _disposed;
|
||
|
||
// 配置 (默认值)
|
||
public TimeSpan WorkDuration { get; set; } = TimeSpan.FromMinutes(20);
|
||
public TimeSpan RestDuration { get; set; } = TimeSpan.FromMinutes(1);
|
||
public TimeSpan IdleThreshold { get; set; } = TimeSpan.FromSeconds(30);
|
||
|
||
public MonitorState CurrentState { get; private set; } = MonitorState.Idle;
|
||
public bool IsPaused { get; private set; } = false;
|
||
|
||
// 事件
|
||
public event EventHandler<TimeSpan>? WorkProgressChanged; // 剩余工作时间
|
||
public event EventHandler<TimeSpan>? RestProgressChanged; // 剩余休息时间
|
||
public event EventHandler? RestStarted;
|
||
public event EventHandler? RestEnded;
|
||
public event EventHandler? StateChanged;
|
||
|
||
public ActivityMonitor()
|
||
{
|
||
_timer = new System.Windows.Forms.Timer();
|
||
_timer.Interval = 1000; // 1秒检查一次
|
||
_timer.Tick += Timer_Tick;
|
||
|
||
SystemEvents.PowerModeChanged += OnPowerModeChanged;
|
||
}
|
||
|
||
public void Start()
|
||
{
|
||
_timer.Start();
|
||
ResetWork();
|
||
}
|
||
|
||
public void Stop()
|
||
{
|
||
_timer.Stop();
|
||
}
|
||
|
||
private void ResetWork()
|
||
{
|
||
_accumulatedWorkTime = TimeSpan.Zero;
|
||
ChangeState(MonitorState.Idle);
|
||
}
|
||
|
||
private void ChangeState(MonitorState newState)
|
||
{
|
||
if (CurrentState != newState)
|
||
{
|
||
CurrentState = newState;
|
||
StateChanged?.Invoke(this, EventArgs.Empty);
|
||
}
|
||
}
|
||
|
||
private void Timer_Tick(object? sender, EventArgs e)
|
||
{
|
||
// 如果处于暂停状态,不处理计时逻辑
|
||
if (_isPaused)
|
||
{
|
||
return;
|
||
}
|
||
|
||
long idleMs = NativeMethods.GetIdleTime();
|
||
TimeSpan idleTime = TimeSpan.FromMilliseconds(idleMs);
|
||
|
||
if (CurrentState == MonitorState.Resting)
|
||
{
|
||
// 休息模式逻辑
|
||
// 使用计数器而不是时间差,避免秒数跳变
|
||
_restElapsedSeconds++;
|
||
int totalRestSeconds = (int)RestDuration.TotalSeconds;
|
||
int remainingSeconds = totalRestSeconds - _restElapsedSeconds;
|
||
|
||
if (remainingSeconds <= 0)
|
||
{
|
||
// 休息结束
|
||
RestEnded?.Invoke(this, EventArgs.Empty);
|
||
ResetWork(); // 重新开始工作周期
|
||
}
|
||
else
|
||
{
|
||
TimeSpan remainingRest = TimeSpan.FromSeconds(remainingSeconds);
|
||
RestProgressChanged?.Invoke(this, remainingRest);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 检测是否有视频/媒体正在播放
|
||
bool isMediaPlaying = NativeMethods.IsMediaPlaying();
|
||
|
||
// 工作/空闲模式逻辑
|
||
if (idleTime > IdleThreshold || isMediaPlaying)
|
||
{
|
||
// 用户离开了或正在播放视频
|
||
if (CurrentState == MonitorState.Working)
|
||
{
|
||
// 如果正在工作,但离开了,暂停工作计时?
|
||
// 简单起见,如果离开时间过长,可以视为一种“休息”,或者只是暂停累积
|
||
// 这里我们简单处理:如果空闲时间超过阈值,状态变为空闲
|
||
ChangeState(MonitorState.Idle);
|
||
}
|
||
|
||
// 如果在 Idle 状态,且空闲时间非常长(比如超过了休息时间),
|
||
// 是否应该重置工作计时器?
|
||
// 假设用户去开会了1小时,回来应该重新计算20分钟。
|
||
if (idleTime > RestDuration)
|
||
{
|
||
_accumulatedWorkTime = TimeSpan.Zero;
|
||
}
|
||
|
||
// 如果正在播放视频,不累加工作时间,但保持当前状态
|
||
if (isMediaPlaying && CurrentState == MonitorState.Working)
|
||
{
|
||
// 保持当前剩余时间不变(不累加,也不减少)
|
||
TimeSpan remainingWork = WorkDuration - _accumulatedWorkTime;
|
||
WorkProgressChanged?.Invoke(this, remainingWork);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 用户在活动且没有播放视频
|
||
if (CurrentState == MonitorState.Idle)
|
||
{
|
||
// 从空闲变为工作
|
||
ChangeState(MonitorState.Working);
|
||
}
|
||
|
||
// 累加工作时间
|
||
// 简单的累加逻辑:这一秒是工作的
|
||
_accumulatedWorkTime += TimeSpan.FromSeconds(1);
|
||
|
||
// 检查是否达到工作时长
|
||
TimeSpan remainingWork = WorkDuration - _accumulatedWorkTime;
|
||
|
||
if (remainingWork <= TimeSpan.Zero)
|
||
{
|
||
// 触发休息
|
||
_restElapsedSeconds = 0; // 重置休息计数器
|
||
ChangeState(MonitorState.Resting);
|
||
RestStarted?.Invoke(this, EventArgs.Empty);
|
||
}
|
||
else
|
||
{
|
||
WorkProgressChanged?.Invoke(this, remainingWork);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public void RefreshStatus()
|
||
{
|
||
if (CurrentState == MonitorState.Working)
|
||
{
|
||
TimeSpan remaining = WorkDuration - _accumulatedWorkTime;
|
||
WorkProgressChanged?.Invoke(this, remaining);
|
||
}
|
||
else if (CurrentState == MonitorState.Resting)
|
||
{
|
||
// 使用计数器计算剩余时间,保持一致性
|
||
int totalRestSeconds = (int)RestDuration.TotalSeconds;
|
||
int remainingSeconds = totalRestSeconds - _restElapsedSeconds;
|
||
if (remainingSeconds < 0) remainingSeconds = 0;
|
||
TimeSpan remaining = TimeSpan.FromSeconds(remainingSeconds);
|
||
RestProgressChanged?.Invoke(this, remaining);
|
||
}
|
||
}
|
||
|
||
public void Restart()
|
||
{
|
||
_accumulatedWorkTime = TimeSpan.Zero;
|
||
_isPaused = false;
|
||
IsPaused = false;
|
||
|
||
// Ensure timer is running
|
||
if (!_timer.Enabled) _timer.Start();
|
||
|
||
// Force state to Working since user manually restarted
|
||
ChangeState(MonitorState.Working);
|
||
|
||
// Immediately refresh UI to show full duration
|
||
RefreshStatus();
|
||
}
|
||
|
||
public void Pause()
|
||
{
|
||
if (!_isPaused && CurrentState != MonitorState.Idle)
|
||
{
|
||
_isPaused = true;
|
||
IsPaused = true;
|
||
|
||
StateChanged?.Invoke(this, EventArgs.Empty);
|
||
}
|
||
}
|
||
|
||
public void Resume()
|
||
{
|
||
if (_isPaused)
|
||
{
|
||
_isPaused = false;
|
||
IsPaused = false;
|
||
|
||
StateChanged?.Invoke(this, EventArgs.Empty);
|
||
RefreshStatus();
|
||
}
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
if (_disposed) return;
|
||
_disposed = true;
|
||
|
||
SystemEvents.PowerModeChanged -= OnPowerModeChanged;
|
||
|
||
_timer.Stop();
|
||
_timer.Tick -= Timer_Tick;
|
||
_timer.Dispose();
|
||
}
|
||
|
||
private void OnPowerModeChanged(object sender, PowerModeChangedEventArgs e)
|
||
{
|
||
if (e.Mode == PowerModes.Resume)
|
||
{
|
||
// 系统唤醒时,强制重置媒体播放检测状态,
|
||
// 避免因检测线程挂起导致一直误报“正在播放”而无法进入工作状态。
|
||
NativeMethods.InvalidateMediaCache();
|
||
}
|
||
}
|
||
}
|
||
}
|