< Summary

Line coverage
100%
Covered lines: 480
Uncovered lines: 0
Coverable lines: 480
Total lines: 803
Line coverage: 100%
Branch coverage
100%
Covered branches: 34
Total branches: 34
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
File 1: HandleConsentOptionSelect()100%11100%
File 2: get_TaskService()100%11100%
File 2: get_Logger()100%11100%
File 2: get_TimerService()100%11100%
File 2: get_ConsentService()100%11100%
File 2: get_NotificationService()100%11100%
File 2: get_ActivityService()100%11100%
File 2: get_PipTimerService()100%11100%
File 2: get_AppState()100%11100%
File 2: get_JSRuntime()100%11100%
File 2: get_KeyboardShortcutService()100%11100%
File 2: get_TodayStatsService()100%11100%
File 2: get_IndexPagePresenterService()100%11100%
File 2: get_Tasks()100%11100%
File 2: get_CurrentTaskId()100%11100%
File 2: get_RemainingTime()100%11100%
File 2: get_CurrentSessionType()100%11100%
File 2: get_IsTimerRunning()100%11100%
File 2: get_IsTimerPaused()100%11100%
File 2: get_IsTimerStarted()100%11100%
File 2: get_IsConsentModalVisible()100%11100%
File 2: get_ConsentCountdown()100%11100%
File 2: get_ConsentOptions()100%11100%
File 2: get_ShowKeyboardHelp()100%11100%
File 2: get_ErrorMessage()100%11100%
File 2: get_IsPipOpen()100%11100%
File 2: InvalidateTodayStatsCache()100%11100%
File 2: get_TodayTotalFocusMinutes()100%11100%
File 2: get_TodayPomodoroCount()100%11100%
File 2: get_TodayTasksWorkedOn()100%11100%
File 2: GetTodayStats()100%22100%
File 2: OnInitializedAsync()100%11100%
File 2: <OnInitializedAsync()100%11100%
File 2: CheckPendingNotificationActionAsync()100%22100%
File 2: UpdateState()100%11100%
File 2: Dispose()100%44100%
File 2: UnsubscribeFromAllServices()100%1212100%
File 2: UnregisterKeyboardShortcuts()100%22100%
File 3: SafeAsync(...)100%11100%
File 3: SafeAsyncInternal()100%11100%
File 3: OnTaskServiceChanged()100%11100%
File 3: OnTimerTick()100%11100%
File 3: OnTimerComplete(...)100%11100%
File 3: OnTimerStateChanged()100%11100%
File 3: OnNotificationAction(...)100%11100%
File 3: OnConsentRequired()100%11100%
File 3: OnConsentCountdownTick()100%11100%
File 3: OnConsentHandled()100%11100%
File 3: OnActivityChanged()100%11100%
File 3: OnPipOpened()100%11100%
File 3: OnPipClosed()100%11100%
File 4: TryExecuteAsync()100%11100%
File 4: HandleTaskAdd()100%11100%
File 4: <HandleTaskAdd()100%11100%
File 4: HandleTaskSelect()100%11100%
File 4: <HandleTaskSelect()100%11100%
File 4: HandleTaskComplete()100%11100%
File 4: <HandleTaskComplete()100%11100%
File 4: HandleTaskDelete()100%11100%
File 4: <HandleTaskDelete()100%11100%
File 4: HandleTaskUncomplete()100%11100%
File 4: <HandleTaskUncomplete()100%11100%
File 5: get_TimerThemeFormatter()100%11100%
File 5: HandleTimerStart()100%66100%
File 5: HandleTimerPause()100%11100%
File 5: HandleTimerResume()100%11100%
File 5: HandleTimerReset()100%11100%
File 5: HandleSessionSwitch()100%11100%
File 5: HandleTogglePip()100%44100%
File 5: GetTimerThemeClass()100%11100%

File(s)

/home/runner/work/Pomodoro/Pomodoro/src/Pomodoro.Web/Pages/Index.razor.Consent.cs

#LineLine coverage
 1using Pomodoro.Web.Models;
 2
 3namespace Pomodoro.Web.Pages;
 4
 5/// <summary>
 6/// Consent actions partial for Index page
 7/// Contains consent modal-related event handlers
 8/// </summary>
 9public partial class IndexBase
 10{
 11    #region Consent Actions
 12
 13    /// <summary>
 14    /// Handles selecting a consent option after timer completion
 15    /// </summary>
 16    public async Task HandleConsentOptionSelect(SessionType sessionType)
 817    {
 18        try
 819        {
 820            await ConsentService.SelectOptionAsync(sessionType);
 621        }
 222        catch (Exception ex)
 223        {
 224            ErrorMessage = $"{Constants.Messages.ErrorSelectingConsentOption}: {ex.Message}";
 225        }
 826    }
 27
 28    #endregion
 29}

/home/runner/work/Pomodoro/Pomodoro/src/Pomodoro.Web/Pages/Index.razor.cs

#LineLine coverage
 1using Microsoft.AspNetCore.Components;
 2using Microsoft.JSInterop;
 3using Microsoft.Extensions.Logging;
 4using Pomodoro.Web.Models;
 5using Pomodoro.Web.Services;
 6
 7namespace Pomodoro.Web.Pages;
 8
 9/// <summary>
 10/// Main partial for Index page
 11/// Contains dependency injection, state management, and lifecycle methods
 12/// </summary>
 13public partial class IndexBase : ComponentBase, IDisposable
 14{
 15    #region Services (Dependency Injection)
 16
 17    [Inject]
 173118    protected ITaskService TaskService { get; set; } = default!;
 19
 20    [Inject]
 94421    protected ILogger<IndexBase> Logger { get; set; } = default!;
 22
 23    [Inject]
 282624    protected ITimerService TimerService { get; set; } = default!;
 25
 26    [Inject]
 278327    protected IConsentService ConsentService { get; set; } = default!;
 28
 29    [Inject]
 162730    protected INotificationService NotificationService { get; set; } = default!;
 31
 32    [Inject]
 135533    protected IActivityService ActivityService { get; set; } = default!;
 34
 35    [Inject]
 218436    protected IPipTimerService PipTimerService { get; set; } = default!;
 37
 38    [Inject]
 54639    protected AppState AppState { get; set; } = default!;
 40
 41    [Inject]
 81242    protected IJSRuntime JSRuntime { get; set; } = default!;
 43
 44    [Inject]
 457145    protected IKeyboardShortcutService KeyboardShortcutService { get; set; } = default!;
 46
 47    [Inject]
 81948    protected ITodayStatsService TodayStatsService { get; set; } = default!;
 49
 50    [Inject]
 87951    protected IndexPagePresenterService IndexPagePresenterService { get; set; } = default!;
 52
 53    #endregion
 54
 55    #region State
 56
 125557    protected List<TaskItem> Tasks { get; set; } = new();
 130858    protected Guid? CurrentTaskId { get; set; }
 93059    protected TimeSpan RemainingTime { get; set; } = TimeSpan.FromMinutes(Constants.Timer.DefaultPomodoroMinutes);
 420360    public SessionType CurrentSessionType { get; set; } = SessionType.Pomodoro;
 98361    protected bool IsTimerRunning { get; set; }
 65862    protected bool IsTimerPaused { get; set; }
 65863    protected bool IsTimerStarted { get; set; }
 32964    protected bool IsConsentModalVisible { get; set; }
 32965    protected int ConsentCountdown { get; set; }
 59966    protected List<ConsentOption> ConsentOptions { get; set; } = new();
 33967    protected bool ShowKeyboardHelp { get; set; }
 48468    public string? ErrorMessage { get; set; }
 34169    public bool IsPipOpen { get; set; }
 70
 71    private (int TotalFocusMinutes, int PomodoroCount, int TasksWorkedOn)? _cachedTodayStats;
 72
 373    private void InvalidateTodayStatsCache() => _cachedTodayStats = null;
 74
 35075    protected int TodayTotalFocusMinutes => GetTodayStats().TotalFocusMinutes;
 32576    protected int TodayPomodoroCount => GetTodayStats().PomodoroCount;
 32577    protected int TodayTasksWorkedOn => GetTodayStats().TasksWorkedOn;
 78
 79    private (int TotalFocusMinutes, int PomodoroCount, int TasksWorkedOn) GetTodayStats()
 100080    {
 100081        return _cachedTodayStats ??= TodayStatsService.GetTodayStats();
 97582    }
 83
 84    #endregion
 85
 86    #region Lifecycle Methods
 87
 88    protected override async Task OnInitializedAsync()
 27289    {
 90        try
 27291        {
 92            // Initialize notification service
 27293            await NotificationService.InitializeAsync();
 94
 95            // Initialize PiP timer service
 26896            await PipTimerService.InitializeAsync();
 97
 98            // Subscribe to service events
 26799            TaskService.OnChange += OnTaskServiceChanged;
 267100            TimerService.OnTick += OnTimerTick;
 267101            TimerService.OnTimerComplete += OnTimerComplete;
 267102            TimerService.OnStateChanged += OnTimerStateChanged;
 267103            ConsentService.OnConsentRequired += OnConsentRequired;
 267104            ConsentService.OnCountdownTick += OnConsentCountdownTick;
 267105            ConsentService.OnConsentHandled += OnConsentHandled;
 106
 107            // Subscribe to notification action events
 267108            NotificationService.OnNotificationAction += OnNotificationAction;
 109
 110            // Subscribe to activity changes to refresh today's summary
 267111            ActivityService.OnActivityChanged += OnActivityChanged;
 112
 113            // Subscribe to PiP events
 267114            PipTimerService.OnPipOpened += OnPipOpened;
 267115            PipTimerService.OnPipClosed += OnPipClosed;
 116
 117            // Register keyboard shortcuts with proper error handling
 267118            KeyboardShortcutService.RegisterShortcut("space", () =>
 3119            {
 3120                SafeTaskRunner.RunAndForget(
 3121                    async () =>
 3122                    {
 3123                        if (TimerService.IsRunning)
 1124                        {
 1125                            await TimerService.PauseAsync();
 1126                        }
 2127                        else if (TimerService.IsPaused)
 1128                        {
 1129                            await TimerService.ResumeAsync();
 1130                        }
 3131                        else
 1132                        {
 1133                            await TimerService.StartPomodoroAsync();
 1134                        }
 3135                    },
 3136                    Logger,
 3137                    Constants.SafeTaskOperations.KeyboardShortcutPlayPause
 3138                );
 270139            }, Constants.KeyboardShortcuts.PlayPauseDescription);
 140
 267141            KeyboardShortcutService.RegisterShortcut("r", () =>
 1142            {
 1143                SafeTaskRunner.RunAndForget(
 1144                    () => TimerService.ResetAsync(),
 1145                    Logger,
 1146                    Constants.SafeTaskOperations.KeyboardShortcutReset
 1147                );
 268148            }, Constants.KeyboardShortcuts.ResetDescription);
 149
 150            // Session switching shortcuts
 267151            KeyboardShortcutService.RegisterShortcut("p", () =>
 1152            {
 1153                SafeTaskRunner.RunAndForget(
 1154                    () => TimerService.StartPomodoroAsync(),
 1155                    Logger,
 1156                    Constants.SafeTaskOperations.KeyboardShortcutPomodoro
 1157                );
 268158            }, Constants.KeyboardShortcuts.PomodoroDescription);
 159
 267160            KeyboardShortcutService.RegisterShortcut("s", () =>
 1161            {
 1162                SafeTaskRunner.RunAndForget(
 1163                    () => TimerService.StartShortBreakAsync(),
 1164                    Logger,
 1165                    Constants.SafeTaskOperations.KeyboardShortcutShortBreak
 1166                );
 268167            }, Constants.KeyboardShortcuts.ShortBreakDescription);
 168
 267169            KeyboardShortcutService.RegisterShortcut("l", () =>
 1170            {
 1171                SafeTaskRunner.RunAndForget(
 1172                    () => TimerService.StartLongBreakAsync(),
 1173                    Logger,
 1174                    Constants.SafeTaskOperations.KeyboardShortcutLongBreak
 1175                );
 268176            }, Constants.KeyboardShortcuts.LongBreakDescription);
 177
 178            // Help shortcut
 267179            KeyboardShortcutService.RegisterShortcut("?", () =>
 2180            {
 2181                ShowKeyboardHelp = true;
 2182                StateHasChanged();
 269183            }, Constants.KeyboardShortcuts.HelpDescription);
 184
 185            // Escape shortcut - close keyboard help modal
 267186            KeyboardShortcutService.RegisterShortcut("escape", () =>
 2187            {
 2188                if (ShowKeyboardHelp)
 1189                {
 1190                    ShowKeyboardHelp = false;
 1191                    StateHasChanged();
 1192                }
 269193            }, "Close keyboard shortcuts");
 194
 195            // Load initial state
 267196            UpdateState();
 197
 198            // Check for pending notification action from URL
 199            // Delay slightly to ensure all services are ready
 200            // Using SafeTaskRunner for proper exception handling
 267201            SafeTaskRunner.RunAndForget(
 267202                async () =>
 267203                {
 267204                    await Task.Delay(Constants.UI.NotificationCheckDelayMs);
 263205                    await CheckPendingNotificationActionAsync();
 261206                },
 267207                Logger,
 267208                Constants.SafeTaskOperations.CheckPendingNotificationAction
 267209            );
 267210        }
 5211        catch (Exception ex)
 5212        {
 5213            ErrorMessage = $"{Constants.Messages.ErrorInitializing}: {ex.Message}";
 5214        }
 272215    }
 216
 217    /// <summary>
 218    /// Check for pending notification action from URL parameter
 219    /// This handles the case when the app is opened from a notification click
 220    /// </summary>
 221    private async Task CheckPendingNotificationActionAsync()
 265222    {
 223        try
 265224        {
 225            // Check URL parameter (set by service worker when opening new window)
 265226            var urlAction = await JSRuntime.InvokeAsync<string>(Constants.JsFunctions.GetUrlParameter, Constants.UrlPara
 147227            if (!string.IsNullOrEmpty(urlAction))
 3228            {
 3229                var decodedAction = Uri.UnescapeDataString(urlAction);
 230                // Clean up URL
 3231                await JSRuntime.InvokeVoidAsync(Constants.JsFunctions.RemoveUrlParameter, Constants.UrlParameters.Notifi
 232                // Process the action
 6233                await InvokeAsync(() => OnNotificationAction(decodedAction));
 3234            }
 147235        }
 118236        catch (Exception ex)
 118237        {
 118238            Logger.LogError(ex, Constants.Messages.ErrorCheckingPendingNotificationAction);
 116239        }
 263240    }
 241
 242    #endregion
 243
 244    #region Helper Methods
 245
 246    private void UpdateState()
 334247    {
 334248        var state = IndexPagePresenterService.UpdateState(TaskService, TimerService);
 249
 333250        Tasks = state.Tasks;
 333251        CurrentTaskId = state.CurrentTaskId;
 333252        RemainingTime = state.RemainingTime;
 333253        CurrentSessionType = state.CurrentSessionType;
 333254        IsTimerRunning = state.IsTimerRunning;
 333255        IsTimerPaused = state.IsTimerPaused;
 333256        IsTimerStarted = state.IsTimerStarted;
 333257    }
 258
 259    #endregion
 260
 261    #region Cleanup
 262
 263    private bool _isDisposed;
 264
 265    public void Dispose()
 276266    {
 280267        if (_isDisposed) return;
 272268        _isDisposed = true;
 269
 270        try
 272271        {
 272272            UnsubscribeFromAllServices();
 272273            UnregisterKeyboardShortcuts();
 269274        }
 3275        catch (Exception ex)
 3276        {
 3277            Logger?.LogError(ex, Constants.Messages.ErrorInDispose);
 3278        }
 276279    }
 280
 281    private void UnsubscribeFromAllServices()
 272282    {
 272283        if (TaskService != null)
 272284            TaskService.OnChange -= OnTaskServiceChanged;
 272285        if (TimerService != null)
 272286        {
 272287            TimerService.OnTick -= OnTimerTick;
 272288            TimerService.OnTimerComplete -= OnTimerComplete;
 272289            TimerService.OnStateChanged -= OnTimerStateChanged;
 272290        }
 272291        if (ConsentService != null)
 272292        {
 272293            ConsentService.OnConsentRequired -= OnConsentRequired;
 272294            ConsentService.OnCountdownTick -= OnConsentCountdownTick;
 272295            ConsentService.OnConsentHandled -= OnConsentHandled;
 272296        }
 272297        if (NotificationService != null)
 272298            NotificationService.OnNotificationAction -= OnNotificationAction;
 272299        if (ActivityService != null)
 272300            ActivityService.OnActivityChanged -= OnActivityChanged;
 272301        if (PipTimerService != null)
 272302        {
 272303            PipTimerService.OnPipOpened -= OnPipOpened;
 272304            PipTimerService.OnPipClosed -= OnPipClosed;
 272305        }
 272306    }
 307
 308    private void UnregisterKeyboardShortcuts()
 272309    {
 272310        if (KeyboardShortcutService != null)
 272311        {
 272312            KeyboardShortcutService.UnregisterShortcut("space");
 269313            KeyboardShortcutService.UnregisterShortcut("r");
 269314            KeyboardShortcutService.UnregisterShortcut("p");
 269315            KeyboardShortcutService.UnregisterShortcut("s");
 269316            KeyboardShortcutService.UnregisterShortcut("l");
 269317            KeyboardShortcutService.UnregisterShortcut("?");
 269318            KeyboardShortcutService.UnregisterShortcut("escape");
 269319        }
 269320    }
 321
 322    #endregion
 323}

/home/runner/work/Pomodoro/Pomodoro/src/Pomodoro.Web/Pages/Index.razor.Events.cs

#LineLine coverage
 1using Microsoft.AspNetCore.Components;
 2using Microsoft.JSInterop;
 3using Pomodoro.Web.Models;
 4using Pomodoro.Web.Services;
 5
 6namespace Pomodoro.Web.Pages;
 7
 8/// <summary>
 9/// Event handlers partial for Index page
 10/// Contains all service event subscription handlers
 11/// </summary>
 12public partial class IndexBase
 13{
 14    #region Safe Async Helper
 15
 16    /// <summary>
 17    /// Safely executes an async operation from event handlers.
 18    /// Prevents unhandled exceptions from crashing the application.
 19    /// </summary>
 20    public void SafeAsync(Func<Task> action, string handlerName)
 3421    {
 3422        _ = SafeAsyncInternal(action, handlerName);
 3423    }
 24
 25    public async Task SafeAsyncInternal(Func<Task> action, string handlerName)
 3626    {
 27        try
 3628        {
 3629            await action();
 3430        }
 231        catch (Exception ex)
 232        {
 233            Logger.LogError(ex, Constants.Messages.LogHandlerErrorFormat, handlerName);
 234        }
 3635    }
 36
 37    #endregion
 38
 39    #region Task Service Events
 40
 41    public void OnTaskServiceChanged()
 342    {
 343        SafeAsync(async () =>
 344        {
 345            UpdateState();
 346            await InvokeAsync(StateHasChanged);
 647        }, nameof(OnTaskServiceChanged));
 348    }
 49
 50    #endregion
 51
 52    #region Timer Service Events
 53
 54    public void OnTimerTick()
 355    {
 356        SafeAsync(async () =>
 357        {
 358            UpdateState();
 359            await InvokeAsync(StateHasChanged);
 660        }, nameof(OnTimerTick));
 361    }
 62
 63    public void OnTimerComplete(SessionType sessionType)
 764    {
 65        // Must use InvokeAsync to run on UI thread since timer runs on background thread
 766        _ = InvokeAsync(() =>
 767        {
 768            try
 769            {
 770                UpdateState();
 771                // Note: ConsentService.HandleTimerCompleteAsync already handles showing the modal
 772                // based on AutoStartEnabled setting. It will trigger OnConsentRequired event which
 773                // we handle in OnConsentRequired() method below.
 774                // Do NOT call ShowConsentModal directly here - it bypasses the AutoStartEnabled check.
 675                StateHasChanged();
 676            }
 177            catch (Exception ex)
 178            {
 179                Logger.LogError(ex, Constants.Messages.ErrorInOnTimerComplete);
 180            }
 1481        });
 782    }
 83
 84    public void OnTimerStateChanged()
 285    {
 286        SafeAsync(async () =>
 287        {
 288            UpdateState();
 289            await InvokeAsync(StateHasChanged);
 490        }, nameof(OnTimerStateChanged));
 291    }
 92
 93    #endregion
 94
 95    #region Notification Events
 96
 97    public void OnNotificationAction(string action)
 1298    {
 1299        SafeAsync(async () =>
 12100        {
 12101            // Handle notification action clicks from browser notifications
 12102            switch (action)
 12103            {
 12104                case Constants.SessionTypes.ActionShortBreak:
 12105                    // Start short break timer
 5106                    ConsentService.HideConsentModal();
 5107                    await TimerService.StartShortBreakAsync();
 5108                    break;
 12109                case Constants.SessionTypes.ActionLongBreak:
 12110                    // Start long break timer
 2111                    ConsentService.HideConsentModal();
 2112                    await TimerService.StartLongBreakAsync();
 2113                    break;
 12114                case Constants.SessionTypes.ActionStartPomodoro:
 12115                    // Start pomodoro with current task
 2116                    ConsentService.HideConsentModal();
 2117                    await TimerService.StartPomodoroAsync(AppState.CurrentTaskId);
 2118                    break;
 12119                case Constants.SessionTypes.ActionSkip:
 12120                    // Do nothing - consent modal stays open, preplanned timer countdown continues
 12121                    // The auto-start will happen when countdown reaches zero
 2122                    break;
 12123            }
 12124            UpdateState();
 12125            await InvokeAsync(StateHasChanged);
 24126        }, nameof(OnNotificationAction));
 12127    }
 128
 129    #endregion
 130
 131    #region Consent Service Events
 132
 133    public void OnConsentRequired()
 2134    {
 2135        SafeAsync(async () =>
 2136        {
 2137            IsConsentModalVisible = ConsentService.IsModalVisible;
 2138            ConsentCountdown = ConsentService.CountdownSeconds;
 2139            ConsentOptions = ConsentService.AvailableOptions;
 2140            await InvokeAsync(StateHasChanged);
 4141        }, nameof(OnConsentRequired));
 2142    }
 143
 144    public void OnConsentCountdownTick()
 2145    {
 2146        SafeAsync(async () =>
 2147        {
 2148            ConsentCountdown = ConsentService.CountdownSeconds;
 2149            await InvokeAsync(StateHasChanged);
 4150        }, nameof(OnConsentCountdownTick));
 2151    }
 152
 153    public void OnConsentHandled()
 2154    {
 2155        SafeAsync(async () =>
 2156        {
 2157            IsConsentModalVisible = false;
 2158            UpdateState();
 2159            await InvokeAsync(StateHasChanged);
 4160        }, nameof(OnConsentHandled));
 2161    }
 162
 163    #endregion
 164
 165    #region Activity Service Events
 166
 167    public void OnActivityChanged()
 3168    {
 3169        SafeAsync(async () =>
 3170        {
 3171            InvalidateTodayStatsCache();
 3172            await InvokeAsync(StateHasChanged);
 6173        }, nameof(OnActivityChanged));
 3174    }
 175
 176    #endregion
 177
 178    #region PiP Timer Events
 179
 180    public void OnPipOpened()
 2181    {
 2182        SafeAsync(async () =>
 2183        {
 2184            IsPipOpen = true;
 2185            await InvokeAsync(StateHasChanged);
 4186        }, nameof(OnPipOpened));
 2187    }
 188
 189    public void OnPipClosed()
 2190    {
 2191        SafeAsync(async () =>
 2192        {
 2193            IsPipOpen = false;
 2194            await InvokeAsync(StateHasChanged);
 4195        }, nameof(OnPipClosed));
 2196    }
 197
 198    #endregion
 199}

/home/runner/work/Pomodoro/Pomodoro/src/Pomodoro.Web/Pages/Index.razor.Tasks.cs

#LineLine coverage
 1namespace Pomodoro.Web.Pages;
 2
 3/// <summary>
 4/// Task actions partial for Index page
 5/// Contains all task-related event handlers
 6/// </summary>
 7public partial class IndexBase
 8{
 9    #region Task Actions
 10
 11    private async Task TryExecuteAsync(Func<Task> action, string errorMessage)
 2812    {
 13        try
 2814        {
 2815            await action();
 216        }
 2617        catch (Exception ex)
 2618        {
 2619            ErrorMessage = $"{errorMessage}: {ex.Message}";
 2620        }
 2821    }
 22
 23    /// <summary>
 24    /// Handles adding a new task
 25    /// </summary>
 26    public async Task HandleTaskAdd(string taskName)
 727    {
 728        await TryExecuteAsync(async () =>
 729        {
 730            await TaskService.AddTaskAsync(taskName);
 331            UpdateState();
 332            StateHasChanged();
 833        }, Constants.Messages.ErrorAddingTask);
 734    }
 35
 36    /// <summary>
 37    /// Handles selecting a task as the current task
 38    /// </summary>
 39    public async Task HandleTaskSelect(Guid taskId)
 640    {
 641        await TryExecuteAsync(async () =>
 642        {
 643            await TaskService.SelectTaskAsync(taskId);
 344            UpdateState();
 345            StateHasChanged();
 746        }, Constants.Messages.ErrorSelectingTask);
 647    }
 48
 49    /// <summary>
 50    /// Handles marking a task as completed
 51    /// </summary>
 52    public async Task HandleTaskComplete(Guid taskId)
 553    {
 554        await TryExecuteAsync(async () =>
 555        {
 556            await TaskService.CompleteTaskAsync(taskId);
 257            UpdateState();
 258            StateHasChanged();
 559        }, Constants.Messages.ErrorCompletingTask);
 560    }
 61
 62    /// <summary>
 63    /// Handles deleting a task (soft delete)
 64    /// </summary>
 65    public async Task HandleTaskDelete(Guid taskId)
 566    {
 567        await TryExecuteAsync(async () =>
 568        {
 569            await TaskService.DeleteTaskAsync(taskId);
 270            UpdateState();
 271            StateHasChanged();
 572        }, Constants.Messages.ErrorDeletingTask);
 573    }
 74
 75    /// <summary>
 76    /// Handles uncompleting a task
 77    /// </summary>
 78    public async Task HandleTaskUncomplete(Guid taskId)
 579    {
 580        await TryExecuteAsync(async () =>
 581        {
 582            await TaskService.UncompleteTaskAsync(taskId);
 283            UpdateState();
 284            StateHasChanged();
 585        }, Constants.Messages.ErrorUncompletingTask);
 586    }
 87
 88    #endregion
 89}

/home/runner/work/Pomodoro/Pomodoro/src/Pomodoro.Web/Pages/Index.razor.Timer.cs

#LineLine coverage
 1using Microsoft.AspNetCore.Components;
 2using Pomodoro.Web.Models;
 3using Pomodoro.Web.Services.Formatters;
 4
 5namespace Pomodoro.Web.Pages;
 6
 7/// <summary>
 8/// Timer actions partial for Index page
 9/// Contains all timer-related event handlers and theme logic
 10/// </summary>
 11public partial class IndexBase
 12{
 87513    [Inject] protected TimerThemeFormatter TimerThemeFormatter { get; set; } = default!;
 14
 15    #region Timer Actions
 16
 17    /// <summary>
 18    /// Handles starting of timer based on current session type
 19    /// </summary>
 20    public async Task HandleTimerStart()
 1321    {
 22        try
 1323        {
 24            // Start timer based on current session type
 1325            switch (CurrentSessionType)
 26            {
 27                case SessionType.Pomodoro:
 28                    // Use TaskService.CurrentTaskId directly to avoid stale local copy
 829                    if (!TaskService.CurrentTaskId.HasValue)
 230                    {
 231                        ErrorMessage = Constants.Messages.SelectTaskBeforePomodoro;
 232                        StateHasChanged();
 133                        return;
 34                    }
 635                    await TimerService.StartPomodoroAsync(TaskService.CurrentTaskId.Value);
 336                    break;
 37                case SessionType.ShortBreak:
 338                    await TimerService.StartShortBreakAsync();
 239                    break;
 40                case SessionType.LongBreak:
 241                    await TimerService.StartLongBreakAsync();
 242                    break;
 43            }
 744            UpdateState();
 745            StateHasChanged();
 146        }
 1147        catch (Exception ex)
 1148        {
 1149            ErrorMessage = $"{Constants.Messages.ErrorStartingTimer}: {ex.Message}";
 1150        }
 1351    }
 52
 53    /// <summary>
 54    /// Handles pausing of timer
 55    /// </summary>
 56    public async Task HandleTimerPause()
 557    {
 58        try
 559        {
 560            await TimerService.PauseAsync();
 361            UpdateState();
 362            StateHasChanged();
 163        }
 464        catch (Exception ex)
 465        {
 466            ErrorMessage = $"{Constants.Messages.ErrorPausingTimer}: {ex.Message}";
 467        }
 568    }
 69
 70    /// <summary>
 71    /// Handles resuming of timer
 72    /// </summary>
 73    public async Task HandleTimerResume()
 574    {
 75        try
 576        {
 577            await TimerService.ResumeAsync();
 378            UpdateState();
 379            StateHasChanged();
 180        }
 481        catch (Exception ex)
 482        {
 483            ErrorMessage = $"{Constants.Messages.ErrorResumingTimer}: {ex.Message}";
 484        }
 585    }
 86
 87    /// <summary>
 88    /// Handles resetting of timer
 89    /// </summary>
 90    public async Task HandleTimerReset()
 591    {
 92        try
 593        {
 594            await TimerService.ResetAsync();
 395            UpdateState();
 396            StateHasChanged();
 197        }
 498        catch (Exception ex)
 499        {
 4100            ErrorMessage = $"{Constants.Messages.ErrorResettingTimer}: {ex.Message}";
 4101        }
 5102    }
 103
 104    /// <summary>
 105    /// Handles switching to a different session type
 106    /// </summary>
 107    public async Task HandleSessionSwitch(SessionType sessionType)
 12108    {
 109        try
 12110        {
 12111            await TimerService.SwitchSessionTypeAsync(sessionType);
 10112            UpdateState();
 10113            StateHasChanged();
 6114        }
 6115        catch (Exception ex)
 6116        {
 6117            ErrorMessage = $"{Constants.Messages.ErrorSwitchingSession}: {ex.Message}";
 6118        }
 12119    }
 120
 121    /// <summary>
 122    /// Handles toggling of Picture-in-Picture timer window
 123    /// </summary>
 124    public async Task HandleTogglePip()
 11125    {
 126        try
 11127        {
 11128            if (PipTimerService.IsOpen)
 3129            {
 3130                await PipTimerService.CloseAsync();
 3131                IsPipOpen = false;
 3132            }
 133            else
 8134            {
 8135                var success = await PipTimerService.OpenAsync();
 6136                if (!success)
 3137                {
 3138                    ErrorMessage = Constants.Messages.PipPopupBlocked;
 3139                }
 6140                IsPipOpen = success;
 6141            }
 9142            StateHasChanged();
 5143        }
 6144        catch (Exception ex)
 6145        {
 6146            ErrorMessage = $"{Constants.Messages.ErrorTogglingFloatingTimer}: {ex.Message}";
 6147        }
 11148    }
 149
 150    #endregion
 151
 152    #region Timer Theme
 153
 154    /// <summary>
 155    /// Gets the CSS class for the current timer theme based on session type
 156    /// </summary>
 157    public string GetTimerThemeClass()
 331158    {
 331159        return TimerThemeFormatter.GetTimerThemeClass(CurrentSessionType);
 331160    }
 161
 162    #endregion
 163}

Methods/Properties

HandleConsentOptionSelect()
get_TaskService()
get_Logger()
get_TimerService()
get_ConsentService()
get_NotificationService()
get_ActivityService()
get_PipTimerService()
get_AppState()
get_JSRuntime()
get_KeyboardShortcutService()
get_TodayStatsService()
get_IndexPagePresenterService()
get_Tasks()
get_CurrentTaskId()
get_RemainingTime()
get_CurrentSessionType()
get_IsTimerRunning()
get_IsTimerPaused()
get_IsTimerStarted()
get_IsConsentModalVisible()
get_ConsentCountdown()
get_ConsentOptions()
get_ShowKeyboardHelp()
get_ErrorMessage()
get_IsPipOpen()
InvalidateTodayStatsCache()
get_TodayTotalFocusMinutes()
get_TodayPomodoroCount()
get_TodayTasksWorkedOn()
GetTodayStats()
OnInitializedAsync()
<OnInitializedAsync()
CheckPendingNotificationActionAsync()
UpdateState()
Dispose()
UnsubscribeFromAllServices()
UnregisterKeyboardShortcuts()
SafeAsync(System.Func`1<System.Threading.Tasks.Task>,System.String)
SafeAsyncInternal()
OnTaskServiceChanged()
OnTimerTick()
OnTimerComplete(Pomodoro.Web.Models.SessionType)
OnTimerStateChanged()
OnNotificationAction(System.String)
OnConsentRequired()
OnConsentCountdownTick()
OnConsentHandled()
OnActivityChanged()
OnPipOpened()
OnPipClosed()
TryExecuteAsync()
HandleTaskAdd()
<HandleTaskAdd()
HandleTaskSelect()
<HandleTaskSelect()
HandleTaskComplete()
<HandleTaskComplete()
HandleTaskDelete()
<HandleTaskDelete()
HandleTaskUncomplete()
<HandleTaskUncomplete()
get_TimerThemeFormatter()
HandleTimerStart()
HandleTimerPause()
HandleTimerResume()
HandleTimerReset()
HandleSessionSwitch()
HandleTogglePip()
GetTimerThemeClass()