Files
TimerApp/ActivityMonitor.cs

247 lines
8.4 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;
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();
}
}
}
}