2026-05-29 01:29:36 +01:00
|
|
|
using System;
|
|
|
|
|
using Avalonia.Controls;
|
|
|
|
|
using Avalonia.Controls.Templates;
|
2026-06-11 00:14:51 +01:00
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
2026-05-29 01:29:36 +01:00
|
|
|
using PostIt.ViewModels;
|
2026-06-11 00:14:51 +01:00
|
|
|
using PostIt.Views;
|
2026-05-29 01:29:36 +01:00
|
|
|
|
|
|
|
|
namespace PostIt;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Given a view model, returns the corresponding view if possible.
|
|
|
|
|
/// </summary>
|
2026-06-11 00:14:51 +01:00
|
|
|
|
2026-05-29 01:29:36 +01:00
|
|
|
public class ViewLocator : IDataTemplate
|
|
|
|
|
{
|
2026-06-11 00:14:51 +01:00
|
|
|
private readonly IServiceProvider _services;
|
2026-05-29 01:29:36 +01:00
|
|
|
|
2026-06-11 00:14:51 +01:00
|
|
|
public ViewLocator(IServiceProvider services)
|
|
|
|
|
{
|
|
|
|
|
_services = services;
|
2026-05-29 01:29:36 +01:00
|
|
|
}
|
|
|
|
|
|
2026-07-07 20:59:26 +01:00
|
|
|
public Control Build(object? data)
|
2026-05-29 01:29:36 +01:00
|
|
|
{
|
2026-06-11 00:14:51 +01:00
|
|
|
return data switch
|
|
|
|
|
{
|
|
|
|
|
MainPageViewModel => _services.GetRequiredService<MainPage>(),
|
2026-07-08 19:03:54 +01:00
|
|
|
Settings => _services.GetRequiredService<SettingsPage>(),
|
2026-06-26 01:45:21 +01:00
|
|
|
HomePageViewModel => _services.GetRequiredService<HomePage>(),
|
feat(postit): signature capture page (dev entry, file persistence)
Builds on f9cfd560 (SignaturePadControl + SignaturePadData) with a
full Avalonia page that captures signatures, renders them as Polylines,
and persists the wire-format payload to ~/.local/share/PostIt/signatures
as JSON v1.
Scope
- New SignaturePage (axaml + code-behind) hosts the render-agnostic
control: a fixed-size Border is the hit-test surface, an overlaid
Canvas is rebuilt on every RedrawRequested from the Strokes buffer.
- SignaturePageViewModel wraps the control: exposes StrokeCount /
PointCount / StatusMessage, Clear and CaptureAsync commands, and
Attach/Detach for view-lifetime ownership.
- CaptureAsync writes a JSON envelope { format, coordinateMax,
capturedAtUtc, strokes, strokeCount } to
LocalApplicationData/PostIt/signatures/signature-yyyyMMdd-HHmmssfff.json.
This is a stop-gap; the production transport will be
POST /api/signature/{devisId} on Yavsc.Api (commit 3+).
- Entry point is a [DEV] button on MainPage that pushes the page
onto the NavigationPage. The production trigger is a SignalR push
from Yavsc.Org ("devis received, sign here") landing on a hub
handler — the button and its Click handler are explicitly marked
dev-only and tracked for removal in the same commit that wires
the SignalR handler.
Plumbing
- App.axaml.cs: SignaturePage and SignaturePageViewModel registered
as Transient in the DI container.
- ViewLocator: routes SignaturePageViewModel to SignaturePage.
- SignaturePadData: adds PointCount (sum of pairs across strokes),
used by the VM status bar and the test surface.
Tests (57/57 green, 9 new in this commit)
- SignaturePageViewModelTests: constructors and dimension validation,
Attach/Detach idempotence, StrokeCompleted and Clear propagate to
the VM, CaptureAsync on empty buffer is a no-op, CaptureAsync on a
non-empty buffer writes a v1 envelope with the expected
structure (parsed back via JsonDocument, not text matching), and
creates the destination directory if missing.
- All previously-green tests (48) remain green.
Out of scope
- POST /api/signature endpoint on Yavsc.Api (commit 3).
- SignalR handler that opens the page on a "devis received" push.
- Rasterization: this commit only proves capture and persistence;
the visible ink is a Polyline reconstruction, not a PNG, by
design (per the wire-format decision in commit 1).
Note on SignaturePadData
- The PointCount property was added after f9cfd560 landed. It is
folded into this commit rather than amending f9cfd560 to keep
the existing history readable; the change is mechanical and
tested by the new SignaturePageViewModelTests.
2026-07-04 15:11:22 +01:00
|
|
|
SignaturePageViewModel => _services.GetRequiredService<SignaturePage>(),
|
2026-08-19 14:09:31 +01:00
|
|
|
AddCircleMemberDialogViewModel => _services.GetRequiredService<AddCircleMemberDialog>(),
|
|
|
|
|
CirclesPageViewModel => _services.GetRequiredService<CirclesPage>(),
|
|
|
|
|
PostAclDialogViewModel => _services.GetRequiredService<PostAclDialog>(),
|
2026-07-07 20:59:26 +01:00
|
|
|
null => new TextBlock { Text = "No view for <null>" },
|
2026-06-26 01:45:21 +01:00
|
|
|
_ => new TextBlock { Text = $"No view for {data.GetType().Name}" }
|
2026-06-11 00:14:51 +01:00
|
|
|
};
|
2026-05-29 01:29:36 +01:00
|
|
|
}
|
2026-06-11 00:14:51 +01:00
|
|
|
|
2026-07-07 20:59:26 +01:00
|
|
|
public bool Match(object? data) => data is ViewModelBase;
|
2026-06-26 01:45:21 +01:00
|
|
|
}
|