< Summary

Information
Class: Pomodoro.Web.Models.AppState
Assembly: Pomodoro.Web
File(s): /home/runner/work/Pomodoro/Pomodoro/src/Pomodoro.Web/Models/AppState.cs
Line coverage
100%
Covered lines: 142
Uncovered lines: 0
Coverable lines: 142
Total lines: 282
Line coverage: 100%
Branch coverage
100%
Covered branches: 40
Total branches: 40
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
get_Tasks()100%11100%
set_Tasks(...)100%66100%
InsertTask(...)100%11100%
UpdateTask(...)100%22100%
FindTaskById(...)100%11100%
get_CurrentTaskId()100%11100%
set_CurrentTaskId(...)100%66100%
get_Settings()100%11100%
set_Settings(...)100%44100%
get_CurrentSession()100%11100%
set_CurrentSession(...)100%22100%
get_TodayTotalFocusMinutes()100%11100%
set_TodayTotalFocusMinutes(...)100%22100%
get_TodayPomodoroCount()100%11100%
set_TodayPomodoroCount(...)100%22100%
get_TodayTaskIdsWorkedOn()100%11100%
set_TodayTaskIdsWorkedOn(...)100%22100%
get_LastResetDate()100%11100%
set_LastResetDate(...)100%66100%
get_CurrentTask()100%22100%
get_TodayTasksWorkedOn()100%11100%
AddTodayTaskId(...)100%22100%
GetCurrentDayKey()100%11100%
NeedsDailyReset()100%22100%
ResetDailyStats()100%11100%
NotifyStateChanged()100%22100%

File(s)

/home/runner/work/Pomodoro/Pomodoro/src/Pomodoro.Web/Models/AppState.cs

#LineLine coverage
 1namespace Pomodoro.Web.Models;
 2
 3/// <summary>
 4/// Central application state container
 5/// Thread-safe implementation for access from multiple services and timer callbacks
 6/// </summary>
 7public class AppState
 8{
 9889    private readonly object _tasksLock = new();
 98810    private List<TaskItem> _tasks = new();
 11
 12    /// <summary>
 13    /// Event raised when state properties change
 14    /// </summary>
 15    public event Action? OnStateChanged;
 16
 17    /// <summary>
 18    /// Thread-safe access to tasks list
 19    /// Returns a copy to prevent external modifications
 20    /// </summary>
 21    public IReadOnlyList<TaskItem> Tasks
 22    {
 23        get
 10124        {
 10125            lock (_tasksLock)
 10126            {
 10127                return _tasks.ToList();
 28            }
 10129        }
 30        set
 7731        {
 7732            lock (_tasksLock)
 7733            {
 7734                _tasks = (value as List<TaskItem>) ?? value?.ToList() ?? new List<TaskItem>();
 7735            }
 7736            NotifyStateChanged();
 7737        }
 38    }
 39
 40    /// <summary>
 41    /// Inserts a task at the specified position (thread-safe)
 42    /// </summary>
 43    public void InsertTask(TaskItem task, int position = 0)
 1744    {
 1745        lock (_tasksLock)
 1746        {
 1747            _tasks.Insert(position, task);
 1748        }
 1749        NotifyStateChanged();
 1750    }
 51
 52    /// <summary>
 53    /// Finds a task by ID and performs an update action (thread-safe)
 54    /// Returns true if the task was found and updated
 55    /// </summary>
 56    public bool UpdateTask(Guid taskId, Action<TaskItem> updateAction)
 2557    {
 2558        lock (_tasksLock)
 2559        {
 5060            var task = _tasks.FirstOrDefault(t => t.Id == taskId);
 2561            if (task != null)
 1962            {
 1963                updateAction(task);
 1964                return true;
 65            }
 666            return false;
 67        }
 2568    }
 69
 70    /// <summary>
 71    /// Finds a task by ID (thread-safe, returns a copy)
 72    /// </summary>
 73    public TaskItem? FindTaskById(Guid taskId)
 2574    {
 2575        lock (_tasksLock)
 2576        {
 4977            return _tasks.FirstOrDefault(t => t.Id == taskId);
 78        }
 2579    }
 80
 81    private Guid? _currentTaskId;
 82    public Guid? CurrentTaskId
 83    {
 4584        get => _currentTaskId;
 85        set
 2586        {
 2587            if (_currentTaskId != value)
 2188            {
 2189                _currentTaskId = value;
 2190                NotifyStateChanged();
 2191            }
 2592        }
 93    }
 94
 98895    private TimerSettings _settings = new();
 96    public TimerSettings Settings
 97    {
 91698        get => _settings;
 99        set
 233100        {
 233101            if (_settings != value)
 193102            {
 193103                _settings = value ?? new TimerSettings();
 193104                NotifyStateChanged();
 193105            }
 233106        }
 107    }
 108
 109    private TimerSession? _currentSession;
 110    public TimerSession? CurrentSession
 111    {
 744112        get => _currentSession;
 113        set
 281114        {
 281115            if (_currentSession != value)
 274116            {
 274117                _currentSession = value;
 274118                NotifyStateChanged();
 274119            }
 281120        }
 121    }
 122
 123    // Today's statistics
 124    private int _todayTotalFocusMinutes;
 125    public int TodayTotalFocusMinutes
 126    {
 81127        get => _todayTotalFocusMinutes;
 128        set
 160129        {
 160130            if (_todayTotalFocusMinutes != value)
 42131            {
 42132                _todayTotalFocusMinutes = value;
 42133                NotifyStateChanged();
 42134            }
 160135        }
 136    }
 137
 138    private int _todayPomodoroCount;
 139    public int TodayPomodoroCount
 140    {
 89141        get => _todayPomodoroCount;
 142        set
 160143        {
 160144            if (_todayPomodoroCount != value)
 42145            {
 42146                _todayPomodoroCount = value;
 42147                NotifyStateChanged();
 42148            }
 160149        }
 150    }
 151
 988152    private readonly object _todayTaskIdsLock = new();
 988153    private List<Guid> _todayTaskIdsWorkedOn = new();
 154
 155    /// <summary>
 156    /// Thread-safe access to today's task IDs
 157    /// </summary>
 158    public List<Guid> TodayTaskIdsWorkedOn
 159    {
 160        get
 84161        {
 84162            lock (_todayTaskIdsLock)
 84163            {
 84164                return _todayTaskIdsWorkedOn.ToList();
 165            }
 84166        }
 167        set
 21168        {
 21169            lock (_todayTaskIdsLock)
 21170            {
 21171                _todayTaskIdsWorkedOn = value ?? new List<Guid>();
 21172            }
 21173            NotifyStateChanged();
 21174        }
 175    }
 176
 177    private DateTime? _lastResetDate;
 178    public DateTime? LastResetDate
 179    {
 92180        get => _lastResetDate;
 181        set
 140182        {
 140183            if (_lastResetDate != value)
 138184            {
 138185                _lastResetDate = value;
 138186                NotifyStateChanged();
 138187            }
 140188        }
 189    }
 190
 191    /// <summary>
 192    /// Gets the currently selected task (thread-safe)
 193    /// </summary>
 194    public TaskItem? CurrentTask
 195    {
 196        get
 6197        {
 6198            lock (_tasksLock)
 6199            {
 6200                return CurrentTaskId.HasValue
 4201                    ? _tasks.FirstOrDefault(t => t.Id == CurrentTaskId.Value)
 6202                    : null;
 203            }
 6204        }
 205    }
 206
 207    /// <summary>
 208    /// Gets the count of unique tasks worked on today (thread-safe)
 209    /// </summary>
 210    public int TodayTasksWorkedOn
 211    {
 212        get
 4213        {
 4214            lock (_todayTaskIdsLock)
 4215            {
 4216                return _todayTaskIdsWorkedOn.Distinct().Count();
 217            }
 4218        }
 219    }
 220
 221    /// <summary>
 222    /// Adds a task ID to today's worked-on list (thread-safe)
 223    /// </summary>
 224    public void AddTodayTaskId(Guid taskId)
 36225    {
 36226        lock (_todayTaskIdsLock)
 36227        {
 36228            if (!_todayTaskIdsWorkedOn.Contains(taskId))
 33229            {
 33230                _todayTaskIdsWorkedOn.Add(taskId);
 33231            }
 36232        }
 36233        NotifyStateChanged();
 36234    }
 235
 236    /// <summary>
 237    /// Gets the current "day" key for daily stats.
 238    /// Uses local midnight for daily reset (e.g., midnight in the user's timezone).
 239    /// Note: For users in Bangladesh (UTC+6), this means stats reset at 6 PM UTC.
 240    /// </summary>
 241    public static DateTime GetCurrentDayKey()
 540242    {
 243        // Use local midnight for daily reset
 540244        return DateTime.Now.Date;
 540245    }
 246
 247    /// <summary>
 248    /// Checks if today's stats need to be reset.
 249    /// Returns true when the local date has changed (crossed local midnight).
 250    /// </summary>
 251    public bool NeedsDailyReset()
 46252    {
 46253        if (LastResetDate == null)
 4254            return true;
 255
 256        // Reset when the local date changes (at local midnight)
 42257        return LastResetDate.Value < GetCurrentDayKey();
 46258    }
 259
 260    /// <summary>
 261    /// Resets daily statistics (thread-safe)
 262    /// </summary>
 263    public void ResetDailyStats()
 118264    {
 118265        TodayTotalFocusMinutes = 0;
 118266        TodayPomodoroCount = 0;
 118267        lock (_todayTaskIdsLock)
 118268        {
 118269            _todayTaskIdsWorkedOn = new List<Guid>();
 118270        }
 118271        LastResetDate = GetCurrentDayKey();
 118272        NotifyStateChanged();
 118273    }
 274
 275    /// <summary>
 276    /// Raises the OnStateChanged event
 277    /// </summary>
 278    public void NotifyStateChanged()
 981279    {
 981280        OnStateChanged?.Invoke();
 981281    }
 282}