Files
TimerApp/ActivityMonitor.cs

263 lines
9.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Windows.Forms;
namespace TimerApp
{
public enum MonitorState
{
Idle,
Working,
Resting
}
public class ActivityMonitor
{
private System.Windows.Forms.Timer _timer;
private DateTime _lastWorkStartTime;
private TimeSpan _accumulatedWorkTime;
private DateTime _restStartTime;
private int _restElapsedSeconds; // 休息已过秒数(用于避免时间计算导致的跳变)
private bool _isPaused = false; // 暂停状态
private DateTime _pauseStartTime; // 暂停开始时间
private TimeSpan _pauseDuration = TimeSpan.Zero; // 累计暂停时间(用于工作计时)
private int _restPauseStartSeconds = 0; // 休息暂停时的已过秒数
// 配置 (默认值)
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;
}
public void Start()
{
_timer.Start();
ResetWork();
}
public void Stop()
{
_timer.Stop();
}
private void ResetWork()
{
_accumulatedWorkTime = TimeSpan.Zero;
_lastWorkStartTime = DateTime.Now;
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);
_lastWorkStartTime = DateTime.Now;
}
// 累加工作时间
// 简单的累加逻辑:这一秒是工作的
_accumulatedWorkTime += TimeSpan.FromSeconds(1);
// 检查是否达到工作时长
TimeSpan remainingWork = WorkDuration - _accumulatedWorkTime;
if (remainingWork <= TimeSpan.Zero)
{
// 触发休息
_restStartTime = DateTime.Now;
_restElapsedSeconds = 0; // 重置休息计数器
ChangeState(MonitorState.Resting);
RestStarted?.Invoke(this, EventArgs.Empty);
}
else
{
WorkProgressChanged?.Invoke(this, remainingWork);
}
}
}
}
// 用于强制重置或测试
public void ForceRest()
{
_restStartTime = DateTime.Now;
_restElapsedSeconds = 0; // 重置休息计数器
ChangeState(MonitorState.Resting);
RestStarted?.Invoke(this, EventArgs.Empty);
}
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;
_lastWorkStartTime = DateTime.Now;
_isPaused = false;
IsPaused = false;
_pauseDuration = TimeSpan.Zero;
// 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;
_pauseStartTime = DateTime.Now;
// 如果正在休息,记录当前已过秒数
if (CurrentState == MonitorState.Resting)
{
_restPauseStartSeconds = _restElapsedSeconds;
}
StateChanged?.Invoke(this, EventArgs.Empty);
}
}
public void Resume()
{
if (_isPaused)
{
_isPaused = false;
IsPaused = false;
// 计算暂停时长
TimeSpan pauseTime = DateTime.Now - _pauseStartTime;
// 如果正在工作,将暂停时间累加到暂停总时长中
// 这样工作时间就不会因为暂停而减少
if (CurrentState == MonitorState.Working)
{
_pauseDuration += pauseTime;
}
// 如果正在休息,调整已过秒数,使剩余时间保持不变
else if (CurrentState == MonitorState.Resting)
{
// 保持已过秒数不变,这样恢复后剩余时间不会变化
// _restElapsedSeconds 保持不变
}
StateChanged?.Invoke(this, EventArgs.Empty);
RefreshStatus();
}
}
}
}