feat(lists): selectable list/folder colors with accent-aware swatches and custom picker

This commit is contained in:
mika kuns
2026-10-01 10:29:53 +02:00
parent d316f3c6a5
commit a6926f53b5
26 changed files with 1529 additions and 45 deletions
@@ -37,6 +37,7 @@ public class ListSettingsModalViewModelTests
public TicketSettingsDto? TicketSettings = new("http://ticket.example", true);
public Func<Task<List<TicketProjectDto>>>? TicketProjectsFactory;
public UpdateListConfigDto? SavedListConfig;
public UpdateListDto? SavedList;
public Exception? GetListConfigThrows;
@@ -52,6 +53,12 @@ public class ListSettingsModalViewModelTests
public override Task<List<TicketProjectDto>> GetTicketProjectsAsync() =>
TicketProjectsFactory?.Invoke() ?? Task.FromResult(new List<TicketProjectDto>());
public override Task UpdateListAsync(UpdateListDto dto)
{
SavedList = dto;
return Task.CompletedTask;
}
public override Task UpdateListConfigAsync(UpdateListConfigDto dto)
{
SavedListConfig = dto;
@@ -95,6 +102,62 @@ public class ListSettingsModalViewModelTests
Assert.False(ListSettingsModalViewModel.IsKnownIcon("mdi:NoSuchIcon"));
}
[Fact]
public void Color_SwatchAndCustomStaySynced()
{
var vm = new ListSettingsModalViewModel(new CountingWorker(), NewFactory());
vm.SelectedSwatch = "Swatch3";
Assert.Equal("Swatch3", vm.SelectedColor);
Assert.False(vm.IsCustomColor);
vm.CustomColor = Avalonia.Media.Color.Parse("#112233");
Assert.Equal("#112233", vm.SelectedColor);
Assert.True(vm.IsCustomColor);
Assert.Null(vm.SelectedSwatch);
vm.SelectedSwatch = null; // what the ListBox pushes while a custom hex is current
Assert.Equal("#112233", vm.SelectedColor);
vm.SelectedSwatch = "";
Assert.Equal("", vm.SelectedColor);
}
[Fact]
public async Task Save_List_SendsColor_AndBlankMeansNull()
{
var worker = new CountingWorker();
var vm = new ListSettingsModalViewModel(worker, NewFactory());
await vm.LoadAsync("l1", "List One", "", "feat", color: "Swatch2");
Assert.Equal("Swatch2", vm.SelectedColor);
await vm.SaveCommand.ExecuteAsync(null);
Assert.Equal("Swatch2", worker.SavedList!.Color);
vm.SelectedSwatch = "";
await vm.SaveCommand.ExecuteAsync(null);
Assert.Null(worker.SavedList!.Color);
}
[Fact]
public async Task Save_Folder_PersistsCustomColor()
{
var factory = NewFactory();
await using (var ctx = await factory.CreateDbContextAsync())
{
await new ClaudeDo.Data.Repositories.ListRepository(ctx).AddFolderAsync(
new ClaudeDo.Data.Models.ListFolderEntity { Id = "f1", Name = "Group", CreatedAt = DateTime.UtcNow });
}
var vm = new ListSettingsModalViewModel(new CountingWorker(), factory);
vm.BeginLoadFolder("f1", "Group", null, "Swatch1");
vm.CustomColor = Avalonia.Media.Color.Parse("#A1B2C3");
await vm.SaveCommand.ExecuteAsync(null);
await using var check = await factory.CreateDbContextAsync();
Assert.Equal("#A1B2C3", (await check.ListFolders.FindAsync("f1"))!.Color);
}
[Fact]
public async Task LoadAsync_CallsGetListConfig_ExactlyOnce()
{