Navigation and DI
This commit is contained in:
parent
e526b050ed
commit
ee2c8452ac
10 changed files with 87 additions and 56 deletions
|
|
@ -1,7 +1,6 @@
|
||||||
<Project>
|
<Project>
|
||||||
<!-- Pull in shared package versions from the repository root. -->
|
<!-- Pull in shared package versions from the repository root. -->
|
||||||
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Packages.props', '$(MSBuildThisFileDirectory)../'))" />
|
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Packages.props', '$(MSBuildThisFileDirectory)../'))" />
|
||||||
|
|
||||||
<!-- PostIt-product-specific versions -->
|
<!-- PostIt-product-specific versions -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Avalonia" Version="12.0.4" />
|
<PackageVersion Include="Avalonia" Version="12.0.4" />
|
||||||
|
|
@ -13,6 +12,7 @@
|
||||||
<PackageVersion Include="Avalonia.Themes.Fluent" Version="12.0.4" />
|
<PackageVersion Include="Avalonia.Themes.Fluent" Version="12.0.4" />
|
||||||
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.2" />
|
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.2" />
|
||||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.2" />
|
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.2" />
|
||||||
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.9" />
|
||||||
<PackageVersion Include="Xamarin.AndroidX.Browser" Version="1.8.0" />
|
<PackageVersion Include="Xamarin.AndroidX.Browser" Version="1.8.0" />
|
||||||
<PackageVersion Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.2.0" />
|
<PackageVersion Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using PostIt.Services;
|
using PostIt.Services;
|
||||||
using PostIt.ViewModels;
|
using PostIt.ViewModels;
|
||||||
using PostIt.Views;
|
using PostIt.Views;
|
||||||
|
|
@ -22,49 +25,52 @@ public partial class App : Application
|
||||||
|
|
||||||
public override void OnFrameworkInitializationCompleted()
|
public override void OnFrameworkInitializationCompleted()
|
||||||
{
|
{
|
||||||
// Single-instance hand-off: if we were launched with a
|
if (TryHandOffCustomSchemeUrl()) return;
|
||||||
// custom-scheme URL on the command line, we are a 2nd
|
|
||||||
// instance whose job is to forward the OAuth2 callback
|
|
||||||
// URL to the running PostIt process and exit. The first
|
|
||||||
// instance is parked inside CustomSchemeBrowser.InvokeAsync
|
|
||||||
// waiting on the named pipe for exactly this message.
|
|
||||||
if (TryHandOffCustomSchemeUrl())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var settings = new Settings();
|
var settings = new Settings();
|
||||||
// Synchronous: Settings.Load is intentionally non-async so we
|
|
||||||
// don't deadlock the Avalonia UI thread. .Wait() on an async
|
|
||||||
// method would block here forever on the await inside the
|
|
||||||
// file read.
|
|
||||||
settings.Load();
|
settings.Load();
|
||||||
|
|
||||||
var tokenStore = new TokenStore(System.IO.Path.Combine(
|
var tokenStore = new TokenStore(System.IO.Path.Combine(
|
||||||
System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData),
|
System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData),
|
||||||
"PostIt", "tokens.json"));
|
"PostIt", "tokens.json"));
|
||||||
var client = new BlogApiClient(new YavscApiClient(settings, tokenStore));
|
|
||||||
|
var api = new YavscApiClient(settings, tokenStore);
|
||||||
|
var client = new BlogApiClient(api);
|
||||||
|
|
||||||
|
// Configure DI
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
|
||||||
|
// Vues
|
||||||
|
services.AddTransient<MainPage>();
|
||||||
|
services.AddTransient<LoginPage>();
|
||||||
|
services.AddTransient<SettingsPage>();
|
||||||
|
services.AddTransient<HomePage>();
|
||||||
|
|
||||||
|
// ViewModels
|
||||||
|
services.AddSingleton(settings);
|
||||||
|
services.AddSingleton(api);
|
||||||
|
services.AddSingleton(client);
|
||||||
|
services.AddTransient<MainPageViewModel>();
|
||||||
|
services.AddTransient<SettingsPageViewModel>();
|
||||||
|
services.AddTransient<LoginPageViewModel>();
|
||||||
|
services.AddTransient<HomePageViewModel>();
|
||||||
|
var provider = services.BuildServiceProvider();
|
||||||
|
|
||||||
|
// Injecter le ViewLocator avec le provider
|
||||||
|
DataTemplates.Clear();
|
||||||
|
DataTemplates.Add(new ViewLocator(provider));
|
||||||
|
|
||||||
|
// Page de départ
|
||||||
|
var homeVm = provider.GetRequiredService<HomePageViewModel>();
|
||||||
|
|
||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
desktop.MainWindow = new MainWindow
|
desktop.MainWindow = new MainWindow { DataContext = homeVm };
|
||||||
{
|
|
||||||
DataContext = new MainPageViewModel(client, settings)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
else if (ApplicationLifetime is IActivityApplicationLifetime singleViewFactoryApplicationLifetime)
|
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleView)
|
||||||
{
|
{
|
||||||
singleViewFactoryApplicationLifetime.MainViewFactory = () =>
|
singleView.MainView = new MainWindow { DataContext = homeVm };
|
||||||
{
|
|
||||||
return new MainPage { DataContext = new MainPageViewModel(client, settings) };
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
|
|
||||||
{
|
|
||||||
singleViewPlatform.MainView = new MainPage
|
|
||||||
{
|
|
||||||
DataContext = new MainPageViewModel(client, settings)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryHandOffCustomSchemeUrl()
|
private bool TryHandOffCustomSchemeUrl()
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" />
|
<PackageReference Include="CommunityToolkit.Mvvm" />
|
||||||
<PackageReference Include="IdentityModel.OidcClient" />
|
<PackageReference Include="IdentityModel.OidcClient" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||||
<ProjectReference Include="../../Yavsc.Abstract/Yavsc.Abstract.csproj" />
|
<ProjectReference Include="../../Yavsc.Abstract/Yavsc.Abstract.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@ public class ViewLocator : IDataTemplate
|
||||||
MainPageViewModel => _services.GetRequiredService<MainPage>(),
|
MainPageViewModel => _services.GetRequiredService<MainPage>(),
|
||||||
SettingsPageViewModel => _services.GetRequiredService<SettingsPage>(),
|
SettingsPageViewModel => _services.GetRequiredService<SettingsPage>(),
|
||||||
LoginPageViewModel => _services.GetRequiredService<LoginPage>(),
|
LoginPageViewModel => _services.GetRequiredService<LoginPage>(),
|
||||||
_ => new TextBlock { Text = $"No view for {data.GetType().Name}" }
|
HomePageViewModel => _services.GetRequiredService<HomePage>(),
|
||||||
|
_ => new TextBlock { Text = $"No view for {data.GetType().Name}" }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,28 @@
|
||||||
|
using PostIt;
|
||||||
namespace PostIt.ViewModels;
|
using PostIt.Services;
|
||||||
|
using PostIt.ViewModels;
|
||||||
|
|
||||||
public class HomePageViewModel : ViewModelBase
|
public class HomePageViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
|
public YavscApiClient Api { get; }
|
||||||
|
public Settings Settings { get; }
|
||||||
|
|
||||||
private string _welcomeText = "Welcome to PostIt!";
|
private string _welcomeText = "Welcome to PostIt!";
|
||||||
public string WelcomeText
|
public string WelcomeText
|
||||||
{
|
{
|
||||||
get => _welcomeText;
|
get => _welcomeText;
|
||||||
set => SetProperty(ref _welcomeText, value);
|
set => SetProperty(ref _welcomeText, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanNavigateNext { get => true; protected set => throw new System.NotImplementedException(); }
|
public override bool CanNavigateNext { get => true; protected set => throw new System.NotImplementedException(); }
|
||||||
public override bool CanNavigatePrevious { get => false; protected set => throw new System.NotImplementedException(); }
|
public override bool CanNavigatePrevious { get => false; protected set => throw new System.NotImplementedException(); }
|
||||||
|
|
||||||
|
public HomePageViewModel(YavscApiClient api, Settings settings)
|
||||||
|
{
|
||||||
|
Api = api;
|
||||||
|
Settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructeur sans arg pour le designer Avalonia
|
||||||
|
public HomePageViewModel() : this(null!, null!) { }
|
||||||
}
|
}
|
||||||
|
|
@ -111,6 +111,10 @@ public partial class LoginPageViewModel : ViewModelBase
|
||||||
private set => this.SetProperty(ref _isBusy, value);
|
private set => this.SetProperty(ref _isBusy, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool _LoginSuccess;
|
||||||
|
public bool LoginSuccess { get => _isBusy;
|
||||||
|
private set => this.SetProperty(ref _LoginSuccess, value); }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Optional override used by tests. When set, this factory is called
|
/// Optional override used by tests. When set, this factory is called
|
||||||
/// instead of <see cref="Platform.CreateBrowser"/> to obtain the
|
/// instead of <see cref="Platform.CreateBrowser"/> to obtain the
|
||||||
|
|
@ -132,6 +136,7 @@ public partial class LoginPageViewModel : ViewModelBase
|
||||||
/// constructing a fresh one.
|
/// constructing a fresh one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public YavscApiClient? ApiClientOverride { get; set; }
|
public YavscApiClient? ApiClientOverride { get; set; }
|
||||||
|
public Action LoginSucceeded { get; internal set; }
|
||||||
|
|
||||||
private YavscApiClient? _api;
|
private YavscApiClient? _api;
|
||||||
|
|
||||||
|
|
@ -171,7 +176,7 @@ public partial class LoginPageViewModel : ViewModelBase
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IsBusy = true;
|
IsBusy = true;
|
||||||
|
LoginSuccess = false;
|
||||||
if (SettingsLoadOverride is not null)
|
if (SettingsLoadOverride is not null)
|
||||||
await SettingsLoadOverride().ConfigureAwait(false);
|
await SettingsLoadOverride().ConfigureAwait(false);
|
||||||
else
|
else
|
||||||
|
|
@ -222,6 +227,8 @@ public partial class LoginPageViewModel : ViewModelBase
|
||||||
IsBusy = false;
|
IsBusy = false;
|
||||||
AccessToken = _api.CurrentAccessToken;
|
AccessToken = _api.CurrentAccessToken;
|
||||||
StatusMessage = "Interactive token acquired.";
|
StatusMessage = "Interactive token acquired.";
|
||||||
|
LoginSuccess = true;
|
||||||
|
LoginSucceeded?.Invoke();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
|
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using System.Threading.Tasks;
|
using PostIt.Services;
|
||||||
|
using PostIt.ViewModels;
|
||||||
|
|
||||||
namespace PostIt.Views;
|
namespace PostIt.Views;
|
||||||
|
|
||||||
|
|
@ -13,11 +13,18 @@ public partial class HomePage : ContentPage
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnLoginClick(object? sender, RoutedEventArgs e)
|
private void OnLoginClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (Navigation is not null)
|
var vm = (HomePageViewModel)DataContext!;
|
||||||
|
var loginVm = new LoginPageViewModel(vm.Settings, apiClient: vm.Api);
|
||||||
await Navigation.PushModalAsync(new LoginPage());
|
loginVm.LoginSucceeded += () =>
|
||||||
|
{
|
||||||
|
var client = new BlogApiClient(vm.Api);
|
||||||
|
Navigation?.PushAsync(new MainPage
|
||||||
|
{
|
||||||
|
DataContext = new MainPageViewModel(client, vm.Settings)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Navigation?.PushAsync(new LoginPage { DataContext = loginVm });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
0
src/PostIt/PostIt/Views/InitialPage.cs
Normal file
0
src/PostIt/PostIt/Views/InitialPage.cs
Normal file
|
|
@ -19,20 +19,15 @@ using Microsoft.AspNetCore.Localization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor;
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Org.BouncyCastle.Crypto;
|
using Org.BouncyCastle.Crypto;
|
||||||
using Org.BouncyCastle.Crypto.Parameters;
|
using Org.BouncyCastle.Crypto.Parameters;
|
||||||
using Org.BouncyCastle.OpenSsl;
|
using Org.BouncyCastle.OpenSsl;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Org.BouncyCastle.Security;
|
using Org.BouncyCastle.Security;
|
||||||
using Microsoft.Extensions.FileProviders;
|
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Yavsc.Helpers;
|
using Yavsc.Helpers;
|
||||||
|
|
@ -44,7 +39,6 @@ using Yavsc.Services;
|
||||||
using Yavsc.Services.Kyc;
|
using Yavsc.Services.Kyc;
|
||||||
using Yavsc.Settings;
|
using Yavsc.Settings;
|
||||||
using Yavsc.ViewModels.Auth;
|
using Yavsc.ViewModels.Auth;
|
||||||
using static IdentityServer8.IdentityServerConstants;
|
|
||||||
using IdentityServer8.Models;
|
using IdentityServer8.Models;
|
||||||
using IdentityServer8.EntityFramework.Mappers;
|
using IdentityServer8.EntityFramework.Mappers;
|
||||||
|
|
||||||
|
|
@ -302,6 +296,7 @@ public static class HostingExtensions
|
||||||
|
|
||||||
// see https://IdentityServer8.readthedocs.io/en/latest/topics/resources.html
|
// see https://IdentityServer8.readthedocs.io/en/latest/topics/resources.html
|
||||||
options.EmitStaticAudienceClaim = true;
|
options.EmitStaticAudienceClaim = true;
|
||||||
|
options.UserInteraction.LoginUrl = "/signin";
|
||||||
|
|
||||||
})
|
})
|
||||||
.AddAspNetIdentity<ApplicationUser>()
|
.AddAspNetIdentity<ApplicationUser>()
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ builder.Services
|
||||||
options.ClaimActions.MapUniqueJsonKey("preferred_username", "preferred_username");
|
options.ClaimActions.MapUniqueJsonKey("preferred_username", "preferred_username");
|
||||||
options.ClaimActions.MapUniqueJsonKey("gender", "gender");
|
options.ClaimActions.MapUniqueJsonKey("gender", "gender");
|
||||||
options.SaveTokens = true;
|
options.SaveTokens = true;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
using (var app = builder.Build())
|
using (var app = builder.Build())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue