using System.Collections.ObjectModel; using Avalonia.Media; using ClaudeDo.Data; using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.EntityFrameworkCore; using TaskStatus = ClaudeDo.Data.Models.TaskStatus; namespace ClaudeDo.Ui.ViewModels; public partial class TaskItemViewModel : ViewModelBase { [ObservableProperty] private string _title; [ObservableProperty] private string _statusText; [ObservableProperty] private string _tagsText; [ObservableProperty] private string _commitType; [ObservableProperty] private string? _description; [ObservableProperty] private TaskStatus _status; [ObservableProperty] private bool _isStarting; [ObservableProperty] private bool _isExpanded; [ObservableProperty] private bool _hasSubtasks; [ObservableProperty] private int _subtaskCount; public ObservableCollection Subtasks { get; } = new(); public string Id { get; } public string ListId { get; } public TaskEntity Entity { get; private set; } private readonly Func? _runNow; private readonly Func _canRunNow; private readonly Func? _toggleDone; private readonly IDbContextFactory _dbFactory; private bool _subtasksLoaded; public TaskItemViewModel(TaskEntity entity, IReadOnlyList tags, Func? runNow, Func canRunNow, IDbContextFactory dbFactory, int subtaskCount, Func? toggleDone = null) { Entity = entity; Id = entity.Id; ListId = entity.ListId; _title = entity.Title; _status = entity.Status; _statusText = entity.Status.ToString().ToLowerInvariant(); _tagsText = string.Join(", ", tags.Select(t => t.Name)); _commitType = entity.CommitType; _description = entity.Description; _runNow = runNow; _canRunNow = canRunNow; _toggleDone = toggleDone; _dbFactory = dbFactory; _subtaskCount = subtaskCount; _hasSubtasks = subtaskCount > 0; } public bool IsDone => Status == TaskStatus.Done; public bool IsRunning => Status == TaskStatus.Running; public bool CanToggleDone => Status != TaskStatus.Running && Status != TaskStatus.Failed; public TextDecorationCollection? TitleDecorations => IsDone ? TextDecorations.Strikethrough : null; public IBrush TitleForeground => IsDone ? new SolidColorBrush(Color.Parse("#5a6578")) : new SolidColorBrush(Color.Parse("#e2e8f0")); public double RowOpacity => IsDone ? 0.6 : 1.0; public void Refresh(TaskEntity entity, IReadOnlyList tags) { Entity = entity; Title = entity.Title; Status = entity.Status; StatusText = entity.Status.ToString().ToLowerInvariant(); TagsText = string.Join(", ", tags.Select(t => t.Name)); CommitType = entity.CommitType; Description = entity.Description; RunNowCommand.NotifyCanExecuteChanged(); OnPropertyChanged(nameof(IsDone)); OnPropertyChanged(nameof(IsRunning)); IsStarting = false; OnPropertyChanged(nameof(CanToggleDone)); OnPropertyChanged(nameof(TitleDecorations)); OnPropertyChanged(nameof(TitleForeground)); OnPropertyChanged(nameof(RowOpacity)); ToggleDoneCommand.NotifyCanExecuteChanged(); } public void SetStarting() { IsStarting = true; StatusText = "starting..."; RunNowCommand.NotifyCanExecuteChanged(); } public void ClearStarting() { IsStarting = false; RunNowCommand.NotifyCanExecuteChanged(); } [RelayCommand(CanExecute = nameof(CanRunNow))] private async Task RunNowAsync() { if (_runNow is not null) await _runNow(Id); } private bool CanRunNow() => _canRunNow() && Status != TaskStatus.Running && !IsStarting; [RelayCommand(CanExecute = nameof(CanToggleDone))] private async Task ToggleDone() { if (_toggleDone is not null) await _toggleDone(Id); } [RelayCommand] private async Task ToggleExpanded() { IsExpanded = !IsExpanded; if (IsExpanded && !_subtasksLoaded) await LoadSubtasksAsync(); } private async Task LoadSubtasksAsync() { using var context = _dbFactory.CreateDbContext(); var repo = new SubtaskRepository(context); var entities = await repo.GetByTaskIdAsync(Id); Subtasks.Clear(); foreach (var e in entities) Subtasks.Add(SubtaskItemViewModel.From(e)); _subtasksLoaded = true; } [RelayCommand] private async Task ToggleSubtaskDone(string subtaskId) { var vm = Subtasks.FirstOrDefault(s => s.Id == subtaskId); if (vm is null) return; vm.Completed = !vm.Completed; using var context = _dbFactory.CreateDbContext(); var entity = await context.Subtasks.FindAsync(subtaskId); if (entity is not null) { entity.Completed = vm.Completed; await context.SaveChangesAsync(); } } public async Task RefreshSubtasksAsync(int newCount) { SubtaskCount = newCount; HasSubtasks = newCount > 0; if (!HasSubtasks) { IsExpanded = false; Subtasks.Clear(); _subtasksLoaded = false; } else if (_subtasksLoaded || IsExpanded) { await LoadSubtasksAsync(); } } }