Compare commits
No commits in common. "0065de7000d7da7e8f8f0e5fb8021b25619c24b6" and "30a0e10bae0922d6e4740a46e311be4522a1001a" have entirely different histories.
0065de7000
...
30a0e10bae
7 changed files with 128 additions and 184 deletions
|
|
@ -5,7 +5,6 @@ using Avalonia.Headless.XUnit;
|
|||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Yavsc.Api.Client;
|
||||
using Yavsc.Blogspot;
|
||||
using PostIt.Services;
|
||||
|
|
@ -79,24 +78,7 @@ public class MainPageButtonsTests
|
|||
{
|
||||
var api = new ThrowingApi();
|
||||
var blog = new BlogApiClient(api, "http://localhost/");
|
||||
var circle = new CircleApiClient(api, "http://localhost/");
|
||||
var acl = new BlogAclApiClient(api, "http://localhost/");
|
||||
// Minimal DI graph: only what MainPageViewModel resolves
|
||||
// when the user clicks a navigation button. Today that's
|
||||
// SignaturePageViewModel / CirclesPageViewModel / ACL
|
||||
// dependencies. The graph intentionally stays local to this
|
||||
// suite to avoid side effects from App.BuildServices() (real
|
||||
// token-store wiring).
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton(new Settings());
|
||||
services.AddSingleton(circle);
|
||||
services.AddSingleton(acl);
|
||||
services.AddTransient<SignaturePageViewModel>();
|
||||
services.AddTransient<CirclesPageViewModel>();
|
||||
services.AddTransient<SignaturePage>();
|
||||
services.AddTransient<CirclesPage>();
|
||||
services.AddTransient<PostAclDialog>();
|
||||
var vm = new MainPageViewModel(blog, services: services.BuildServiceProvider());
|
||||
var vm = new MainPageViewModel(blog);
|
||||
if (selectedPost is not null) vm.SelectedPost = selectedPost;
|
||||
return vm;
|
||||
}
|
||||
|
|
@ -116,13 +98,6 @@ public class MainPageButtonsTests
|
|||
{
|
||||
var window = new MainWindow();
|
||||
var page = new MainPage { DataContext = vm };
|
||||
var app = (PostIt.App)Application.Current!;
|
||||
if (vm.Services is not null)
|
||||
{
|
||||
app.DataTemplates.Clear();
|
||||
app.DataTemplates.Add(new ViewLocator(vm.Services));
|
||||
}
|
||||
app.AttachMainWindow(window);
|
||||
window.Show();
|
||||
window.NavRoot.PushAsync(page).GetAwaiter().GetResult();
|
||||
return (window, page);
|
||||
|
|
@ -145,11 +120,8 @@ public class MainPageButtonsTests
|
|||
private static int ClickAndCapture(MainWindow window, Button button)
|
||||
{
|
||||
var stackBefore = window.NavRoot.NavigationStack.Count;
|
||||
button.Command?.Execute(button.CommandParameter);
|
||||
if (button.Command is IAsyncRelayCommand asyncCommand)
|
||||
{
|
||||
asyncCommand.ExecutionTask?.GetAwaiter().GetResult();
|
||||
}
|
||||
button.Focus();
|
||||
window.KeyPressQwerty(PhysicalKey.Enter, RawInputModifiers.None);
|
||||
return stackBefore;
|
||||
}
|
||||
|
||||
|
|
@ -211,20 +183,16 @@ public class MainPageButtonsTests
|
|||
[AvaloniaFact]
|
||||
public void Signature_dev_button_click_pushes_a_page_onto_nav_stack()
|
||||
{
|
||||
// Arrange: the "[DEV] Signature" button is bound to the
|
||||
// MainPageViewModel.OpenSignatureDevCommand [RelayCommand].
|
||||
// The click must push SignaturePage on top of NavRoot.
|
||||
// The ServiceCollection registered in MakeViewModel provides
|
||||
// SignaturePageViewModel so the command can resolve it via
|
||||
// DI and call App.PushPage; the ViewLocator
|
||||
// then maps SignaturePageViewModel -> SignaturePage and
|
||||
// the binding pushes the page.
|
||||
// Arrange: the "[DEV] Signature" button uses XAML's
|
||||
// Click="OpenSignatureDev" attribute, so we don't bind
|
||||
// a Command here — we drive the click directly. The
|
||||
// handler resolves App.ServiceProvider, which is null
|
||||
// in a unit test, and early-returns; that is the
|
||||
// failure mode the test pins.
|
||||
var vm = MakeViewModel();
|
||||
var (window, page) = MountMainPage(vm);
|
||||
|
||||
var signatureButton = page.OpenSignatureDevButton;
|
||||
Assert.NotNull(signatureButton.Command);
|
||||
Assert.True(signatureButton.Command.CanExecute(null));
|
||||
|
||||
// Act
|
||||
var stackBefore = ClickAndCapture(window, signatureButton);
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@
|
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:PostIt"
|
||||
x:Class="PostIt.App">
|
||||
|
||||
<!-- ViewLocator is registered in App.axaml.cs with the real DI container. -->
|
||||
|
||||
<Application.DataTemplates>
|
||||
<local:ViewLocator/>
|
||||
</Application.DataTemplates>
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Avalonia;
|
||||
|
|
@ -49,11 +48,11 @@ public partial class App : Application
|
|||
// build is ever reconfigured to skip the early check.
|
||||
if (TryHandOffCustomSchemeUrl()) return;
|
||||
|
||||
this.ServiceProvider = BuildServices();
|
||||
AttachServiceProvider(ServiceProvider);
|
||||
var settings = ServiceProvider.GetRequiredService<Settings>();
|
||||
var sessionStatus = ServiceProvider.GetRequiredService<SessionStatusViewModel>();
|
||||
var api = ServiceProvider.GetRequiredService<YavscApiClient>();
|
||||
var serviceProvider = BuildServices();
|
||||
AttachServiceProvider(serviceProvider);
|
||||
var settings = serviceProvider.GetRequiredService<Settings>();
|
||||
var sessionStatus = serviceProvider.GetRequiredService<SessionStatusViewModel>();
|
||||
var api = serviceProvider.GetRequiredService<YavscApiClient>();
|
||||
|
||||
DataTemplates.Clear();
|
||||
DataTemplates.Add(new ViewLocator(ServiceProvider));
|
||||
|
|
@ -87,7 +86,8 @@ public partial class App : Application
|
|||
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
var homeVm = ServiceProvider.GetRequiredService<HomePageViewModel>();
|
||||
var homePage = ServiceProvider.GetRequiredService<HomePage>();
|
||||
homePage.DataContext = ServiceProvider.GetRequiredService<HomePageViewModel>();
|
||||
|
||||
window = new MainWindow();
|
||||
window.SessionBanner.DataContext = sessionStatus;
|
||||
|
|
@ -95,8 +95,9 @@ public partial class App : Application
|
|||
// Build the navigation stack from scratch: HomePage is the
|
||||
// root in both cases. App.BootAsync will push MainPage on
|
||||
// top if the silent refresh succeeds.
|
||||
window.DataContext = homePage.DataContext;
|
||||
desktop.MainWindow = window;
|
||||
_ = PushPageAsync(homeVm);
|
||||
_ = window.NavRoot.PushAsync(homePage);
|
||||
|
||||
// When the user logs out, route back to HomePage. We
|
||||
// ReplaceAsync the current top so we don't grow the stack
|
||||
|
|
@ -106,6 +107,8 @@ public partial class App : Application
|
|||
{
|
||||
var w = (MainWindow)((IClassicDesktopStyleApplicationLifetime)ApplicationLifetime!).MainWindow!;
|
||||
var nav = w.NavRoot;
|
||||
var hp = ServiceProvider.GetRequiredService<HomePage>();
|
||||
hp.DataContext = ServiceProvider.GetRequiredService<HomePageViewModel>();
|
||||
_ = nav.PopToRootAsync();
|
||||
};
|
||||
|
||||
|
|
@ -117,7 +120,35 @@ public partial class App : Application
|
|||
_ = PushMainPageAsync();
|
||||
};
|
||||
|
||||
window.Opened += async (_, _) => await BootAsync(this.ServiceProvider, api);
|
||||
// When the user clicks the "Paramètres" button on the
|
||||
// session banner, push the SettingsPage singleton on top
|
||||
// of the current navigation stack. The DataContext is
|
||||
// already wired at composition time (see the
|
||||
// provider.GetRequiredService<SettingsPage>().DataContext
|
||||
// assignment above), so this handler is a pure
|
||||
// navigation concern.
|
||||
//
|
||||
// Anti-empilement guard: if the SettingsPage is already
|
||||
// at the top of the stack, do nothing. NavigationPage's
|
||||
// PushAsync does not deduplicate; calling it twice with
|
||||
// the same instance would push it a second time and the
|
||||
// user would have to tap Back twice to leave. Reference
|
||||
// comparison is correct here because SettingsPage is a
|
||||
// singleton — there is exactly one instance to compare
|
||||
// against.
|
||||
sessionStatus.OpenSettingsRequested += () =>
|
||||
{
|
||||
var w = (MainWindow)((IClassicDesktopStyleApplicationLifetime)ApplicationLifetime!).MainWindow!;
|
||||
var settingsPage = ServiceProvider.GetRequiredService<SettingsPage>();
|
||||
var stack = w.NavRoot.NavigationStack;
|
||||
if (stack.Count > 0 && ReferenceEquals(stack[stack.Count - 1], settingsPage))
|
||||
{
|
||||
return;
|
||||
}
|
||||
_ = w.NavRoot.PushAsync(settingsPage);
|
||||
};
|
||||
|
||||
window.Opened += async (_, _) => await BootAsync(ServiceProvider, api);
|
||||
}
|
||||
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleView)
|
||||
{
|
||||
|
|
@ -215,17 +246,6 @@ public partial class App : Application
|
|||
Settings.BindToServiceProvider(sp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test-only hook: bind a concrete <see cref="MainWindow"/> so
|
||||
/// command-driven navigation paths (<see cref="PushPage"/>) can
|
||||
/// push onto a real <see cref="NavigationPage"/> in headless
|
||||
/// fixtures that do not run the full desktop lifetime bootstrap.
|
||||
/// </summary>
|
||||
internal void AttachMainWindow(MainWindow mainWindow)
|
||||
{
|
||||
window = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow));
|
||||
}
|
||||
|
||||
private static void ApplyDarkMode(Settings settings)
|
||||
{
|
||||
Application.Current!.RequestedThemeVariant =
|
||||
|
|
@ -253,18 +273,19 @@ public partial class App : Application
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve a fresh <c>MainPageViewModel</c> from DI and push its
|
||||
/// mapped page (via <see cref="ViewLocator"/>) on top
|
||||
/// Resolve a fresh <c>MainPage</c> + VM from DI and push it on top
|
||||
/// of the current navigation stack. Used both by <see cref="BootAsync"/>
|
||||
/// (silent refresh at boot) and by <c>SessionStatusViewModel.LoginSucceeded</c>
|
||||
/// (interactive login from the banner). Pulled out as a helper so
|
||||
/// the two callers can't drift apart.
|
||||
/// </summary>
|
||||
public static Task PushMainPageAsync()
|
||||
public static async Task PushMainPageAsync()
|
||||
{
|
||||
var app = (App)Current;
|
||||
var mainVm = app.ServiceProvider.GetRequiredService<MainPageViewModel>();
|
||||
return app.PushPageAsync(mainVm);
|
||||
var mainPage = app.ServiceProvider.GetRequiredService<MainPage>();
|
||||
mainPage.DataContext = mainVm;
|
||||
await app.window.FindControl<NavigationPage>("NavRoot").PushAsync(mainPage).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private bool TryHandOffCustomSchemeUrl()
|
||||
|
|
@ -299,46 +320,4 @@ public partial class App : Application
|
|||
return true;
|
||||
}
|
||||
|
||||
internal void PushPage(ViewModelBase vm)
|
||||
{
|
||||
_ = PushPageAsync(vm);
|
||||
}
|
||||
|
||||
internal Task PushPageAsync(ViewModelBase vm)
|
||||
{
|
||||
if (window is null)
|
||||
{
|
||||
throw new InvalidOperationException("MainWindow is not initialized yet.");
|
||||
}
|
||||
|
||||
var template = DataTemplates.FirstOrDefault(t => t.Match(vm));
|
||||
if (template is null)
|
||||
{
|
||||
throw new InvalidOperationException($"No IDataTemplate found for {vm.GetType().Name}.");
|
||||
}
|
||||
|
||||
var view = template.Build(vm);
|
||||
if (view is null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Template for {vm.GetType().Name} returned <null>.");
|
||||
}
|
||||
|
||||
if (view is not Page page)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Template for {vm.GetType().Name} returned {view.GetType().Name}, expected a Page.");
|
||||
}
|
||||
|
||||
page.DataContext = vm;
|
||||
|
||||
// Avoid stacking the same singleton page twice (e.g. SettingsPage).
|
||||
var stack = window.NavRoot.NavigationStack;
|
||||
if (stack.Count > 0 && ReferenceEquals(stack[stack.Count - 1], page))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
return window.NavRoot.PushAsync(page);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,11 @@ using System;
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Yavsc.Blogspot;
|
||||
using Yavsc.Api.Client;
|
||||
using PostIt.Services;
|
||||
using PostIt.Views;
|
||||
|
||||
namespace PostIt.ViewModels;
|
||||
|
||||
|
|
@ -50,6 +47,9 @@ public partial class MainPageViewModel : ViewModelBase
|
|||
[ObservableProperty]
|
||||
public partial bool DraftIsPublished { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ViewModelBase? CurrentViewModel { get; set; }
|
||||
|
||||
public Settings SettingsModel { get; }
|
||||
|
||||
[ObservableProperty]
|
||||
|
|
@ -81,46 +81,9 @@ public partial class MainPageViewModel : ViewModelBase
|
|||
/// </summary>
|
||||
public BlogApiClient? BlogClient { get; }
|
||||
|
||||
/// <summary>
|
||||
/// DI container the VM uses to resolve navigation targets
|
||||
/// (other ViewModels) when the user clicks a toolbar button
|
||||
/// that opens a sub-screen. Owned by <c>App.ServiceProvider</c>
|
||||
/// in production; injected directly in tests. The VM resolves
|
||||
/// <em>ViewModels</em> via this provider, never Views — the
|
||||
/// actual <see cref="Control"/> to push is decided by
|
||||
/// <see cref="ViewLocator"/> at bind time, per CONTRIBUTING.md
|
||||
/// §"Navigation (PostIt)".
|
||||
/// </summary>
|
||||
public IServiceProvider? Services { get; }
|
||||
|
||||
private SignaturePageViewModel? _signatureModel;
|
||||
|
||||
/// <summary>
|
||||
/// Resolved on first access. Lazy so the test path (which
|
||||
/// never pushes <c>SignaturePage</c>) does not require a
|
||||
/// fully-built DI graph just to construct the VM. Mirrors the
|
||||
/// pattern of <see cref="SettingsModel"/> for the Settings case.
|
||||
/// </summary>
|
||||
public SignaturePageViewModel SignatureModel =>
|
||||
_signatureModel ??= ResolveSignatureModel();
|
||||
|
||||
public override bool CanNavigateNext { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); }
|
||||
public override bool CanNavigatePrevious { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); }
|
||||
|
||||
private SignaturePageViewModel ResolveSignatureModel()
|
||||
{
|
||||
var sp = ResolveServices();
|
||||
return sp.GetRequiredService<SignaturePageViewModel>();
|
||||
}
|
||||
|
||||
private IServiceProvider ResolveServices()
|
||||
{
|
||||
return Services ?? (Application.Current as App)?.ServiceProvider ??
|
||||
throw new InvalidOperationException(
|
||||
"No IServiceProvider available for navigation. Inject one in tests " +
|
||||
"or ensure App.ServiceProvider is initialized in production.");
|
||||
}
|
||||
|
||||
|
||||
public MainPageViewModel()
|
||||
{
|
||||
|
|
@ -152,6 +115,7 @@ public partial class MainPageViewModel : ViewModelBase
|
|||
DraftTitle = string.Empty;
|
||||
DraftArticle = string.Empty;
|
||||
DraftIsPublished = false;
|
||||
CurrentViewModel = this;
|
||||
}
|
||||
|
||||
/// <summary>Save is enabled as soon as the user has typed
|
||||
|
|
@ -171,11 +135,10 @@ public partial class MainPageViewModel : ViewModelBase
|
|||
/// <see cref="BlogApiClient"/>. Production code uses the
|
||||
/// (Settings, BlogApiClient) overload below.
|
||||
/// </summary>
|
||||
public MainPageViewModel(BlogApiClient blogClient, Settings? settings = null, IServiceProvider? services = null)
|
||||
public MainPageViewModel(BlogApiClient blogClient, Settings? settings = null)
|
||||
{
|
||||
SettingsModel = new Settings();
|
||||
BlogClient = blogClient ?? throw new ArgumentNullException(nameof(blogClient)); ;
|
||||
Services = services;
|
||||
|
||||
Init(settings);
|
||||
}
|
||||
|
|
@ -346,30 +309,15 @@ public partial class MainPageViewModel : ViewModelBase
|
|||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DEV ONLY: open the signature capture page. The production
|
||||
/// entry point is a SignalR push from Yavsc.Org ("devis
|
||||
/// received, sign here"); this command is the dev-time
|
||||
/// shortcut to reach the page without that infrastructure.
|
||||
/// Aligned on the same VM-first navigation pattern as
|
||||
/// <see cref="OpenSettings"/>: the VM resolves the target VM
|
||||
/// through <see cref="Services"/>, the <c>ViewLocator</c> picks
|
||||
/// the matching <c>Control</c> at bind time. No
|
||||
/// <c>Click</code> handler, no <c>App.ServiceProvider</c>
|
||||
/// access from the view layer.
|
||||
/// </summary>
|
||||
[RelayCommand]
|
||||
internal async Task OpenSignatureDev()
|
||||
internal void OpenSettings()
|
||||
{
|
||||
await ((App)App.Current!).PushPageAsync(SignatureModel).ConfigureAwait(true);
|
||||
CurrentViewModel = SettingsModel;
|
||||
}
|
||||
|
||||
private ViewModelBase GetACLViewModel(BlogPostDto selectedPost)
|
||||
private ViewModelBase? GetACLViewModel(BlogPostDto selectedPost)
|
||||
{
|
||||
var sp = ResolveServices();
|
||||
var aclClient = sp.GetRequiredService<BlogAclApiClient>();
|
||||
var circleClient = sp.GetRequiredService<CircleApiClient>();
|
||||
return new PostAclDialogViewModel(selectedPost, aclClient, circleClient);
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private async Task RefreshPostsAsync()
|
||||
|
|
@ -435,16 +383,19 @@ public partial class MainPageViewModel : ViewModelBase
|
|||
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanManageAcl))]
|
||||
public async Task ManageAcl()
|
||||
public void ManageAcl()
|
||||
{
|
||||
if (SelectedPost is null) return;
|
||||
await ((App)App.Current!).PushPageAsync(GetACLViewModel(SelectedPost)).ConfigureAwait(true);
|
||||
CurrentViewModel = GetACLViewModel(SelectedPost);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the user asks to open the circles page (full
|
||||
/// CRUD on their own circles). Same routing as
|
||||
/// <see cref="ManageAclRequested"/>.
|
||||
/// </summary>
|
||||
public event EventHandler? OpenCirclesRequested;
|
||||
|
||||
[RelayCommand]
|
||||
public async Task OpenCircles()
|
||||
{
|
||||
var circlesVm = ResolveServices().GetRequiredService<CirclesPageViewModel>();
|
||||
await ((App)App.Current!).PushPageAsync(circlesVm).ConfigureAwait(true);
|
||||
}
|
||||
public void OpenCircles() => OpenCirclesRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using System;
|
|||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using PostIt.Services;
|
||||
|
||||
namespace PostIt.ViewModels;
|
||||
|
|
@ -33,6 +32,15 @@ public partial class SessionStatusViewModel : ViewModelBase
|
|||
/// <c>HomePage</c> so the user lands on the blog editor.</summary>
|
||||
public event System.Action? LoginSucceeded;
|
||||
|
||||
/// <summary>Raised when the user clicks the "Paramètres" button on
|
||||
/// the session banner. <c>App.axaml.cs</c> listens and pushes
|
||||
/// <c>SettingsPage</c> (resolved from DI, bound to the canonical
|
||||
/// <c>Settings</c> singleton) on top of the current navigation
|
||||
/// stack. Same event pattern as <see cref="LogoutCompleted"/> and
|
||||
/// <see cref="LoginSucceeded"/> so the VM stays decoupled from
|
||||
/// <c>NavigationPage</c> / window lifetime.</summary>
|
||||
public event System.Action? OpenSettingsRequested;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsLoggedIn { get; private set; }
|
||||
|
||||
|
|
@ -136,10 +144,9 @@ public partial class SessionStatusViewModel : ViewModelBase
|
|||
}
|
||||
|
||||
[RelayCommand]
|
||||
internal async Task OpenSettings()
|
||||
public async System.Threading.Tasks.Task OpenSettingsCommand()
|
||||
{
|
||||
var app = (App)App.Current!;
|
||||
await app.PushPageAsync(app.ServiceProvider.GetRequiredService<Settings>()).ConfigureAwait(true);
|
||||
OpenSettingsRequested?.Invoke();
|
||||
await System.Threading.Tasks.Task.CompletedTask;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,8 +59,8 @@
|
|||
MainPage.axaml.cs once the SignalR handler lands.
|
||||
-->
|
||||
<Button x:Name="OpenSignatureDevButton"
|
||||
Command="{Binding OpenSignatureDev}"
|
||||
Content="[DEV] Signature"
|
||||
Click="OpenSignatureDev"
|
||||
ToolTip.Tip="DEV ONLY — to remove when SignalR handler lands" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,11 @@
|
|||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using PostIt.ViewModels;
|
||||
using Yavsc.Blogspot;
|
||||
using Yavsc.Api.Client;
|
||||
|
||||
namespace PostIt.Views;
|
||||
|
||||
|
|
@ -8,4 +15,34 @@ public partial class MainPage : ContentPage
|
|||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
MainPageViewModel? _vm;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// DEV ONLY: temporary shortcut to open the signature capture
|
||||
/// page from the blog editor. The production entry point is a
|
||||
/// SignalR push from Yavsc.Org ("devis received, sign here"),
|
||||
/// which is the only path that carries the devis identifier
|
||||
/// needed to bind the capture to a specific contract.
|
||||
///
|
||||
/// Remove this method and the corresponding button in
|
||||
/// MainPage.axaml.cs once the SignalR handler lands.
|
||||
/// </summary>
|
||||
private void OpenSignatureDev(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
// Resolve via the App's DI container so the page gets
|
||||
// the canonical services (Api client, settings, ...).
|
||||
var app = Application.Current as App;
|
||||
var services = app?.ServiceProvider;
|
||||
if (services is null) return;
|
||||
|
||||
var page = services.GetRequiredService<SignaturePage>();
|
||||
page.DataContext = services.GetRequiredService<SignaturePageViewModel>();
|
||||
|
||||
if (this.VisualRoot is MainWindow window)
|
||||
{
|
||||
_ = window.NavRoot.PushAsync(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue