Tasks with subtasks show a chevron for inline expand/collapse. Subtask checkboxes toggle completion state directly. Also sets Windows AppUserModelID for proper taskbar identity. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
175 lines
5.6 KiB
C#
175 lines
5.6 KiB
C#
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<SubtaskItemViewModel> Subtasks { get; } = new();
|
|
|
|
public string Id { get; }
|
|
public string ListId { get; }
|
|
public TaskEntity Entity { get; private set; }
|
|
|
|
private readonly Func<string, Task>? _runNow;
|
|
private readonly Func<bool> _canRunNow;
|
|
private readonly Func<string, Task>? _toggleDone;
|
|
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
|
private bool _subtasksLoaded;
|
|
|
|
public TaskItemViewModel(TaskEntity entity, IReadOnlyList<TagEntity> tags,
|
|
Func<string, Task>? runNow, Func<bool> canRunNow,
|
|
IDbContextFactory<ClaudeDoDbContext> dbFactory, int subtaskCount,
|
|
Func<string, Task>? 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<TagEntity> 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();
|
|
}
|
|
}
|
|
}
|