diff --git a/src/PostIt/Directory.Packages.props b/src/PostIt/Directory.Packages.props
index 386d8924e..580a8e521 100644
--- a/src/PostIt/Directory.Packages.props
+++ b/src/PostIt/Directory.Packages.props
@@ -4,7 +4,7 @@
true
- 12.1.2
+ 12.1.3
@@ -13,15 +13,15 @@
-
+
-
+
-
+
@@ -36,4 +36,4 @@
-
\ No newline at end of file
+
diff --git a/src/PostIt/PostIt.Desktop/Program.cs b/src/PostIt/PostIt.Desktop/Program.cs
index a2b8cd66f..1c5aa0c27 100644
--- a/src/PostIt/PostIt.Desktop/Program.cs
+++ b/src/PostIt/PostIt.Desktop/Program.cs
@@ -1,6 +1,7 @@
using System;
using Avalonia;
using PostIt.Services;
+using PostIt.CLIOptions;
namespace PostIt.Desktop;
diff --git a/src/PostIt/PostIt.Tests/SessionStatusBannerTests.cs b/src/PostIt/PostIt.Tests/SessionStatusBannerTests.cs
index cc08304d5..3516e93d6 100644
--- a/src/PostIt/PostIt.Tests/SessionStatusBannerTests.cs
+++ b/src/PostIt/PostIt.Tests/SessionStatusBannerTests.cs
@@ -101,7 +101,7 @@ public class SessionStatusBannerTests
{
MainWindow window = new MainWindow();
window.Show();
- var banner = window.MainView.SessionBanner;
+ var banner = new SessionStatusBanner();
var status = new SessionStatusViewModel();
banner.DataContext = status;
return banner;
diff --git a/src/PostIt/PostIt/App.axaml.cs b/src/PostIt/PostIt/App.axaml.cs
index 4769fedf3..5c986d1dd 100644
--- a/src/PostIt/PostIt/App.axaml.cs
+++ b/src/PostIt/PostIt/App.axaml.cs
@@ -14,15 +14,17 @@ using PostIt.Helpers;
using System.Globalization;
using Live.Avalonia;
using System.Diagnostics;
+using System.Linq;
using ReactiveUI;
using System.Reactive;
namespace PostIt;
-public partial class App : Application, ILiveView
+public partial class App : Application, ILiveView, IVMPusherApp
{
+ public static bool LiveMode { get; private set; }
private int _bootStarted;
- public string? CliConfigFileSpecification { get; private set; }
+ public static string? CliConfigFileSpecification { get; private set; }
///
/// DI container the platform entry points hand to ViewModels so
@@ -49,38 +51,57 @@ public partial class App : Application, ILiveView
public override void OnFrameworkInitializationCompleted()
{
-
-#if DEBUG
- if (Debugger.IsAttached && !IsProduction())
- {
- // Debugging requires pdb loading etc, so we disable live reloading
- // during a test run with an attached debugger.
- var window = new Window();
- window.Content = CreateView(window);
- window.Show();
- }
- else
-#endif
- {
if (TryHandOffCustomSchemeUrl()) return;
this.ServiceProvider = new ServiceCollection().BuildPostItServices();
var settings = ServiceProvider.GetRequiredService();
- if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ if (!LiveMode)
{
- InitializeClassicDesktopLifetime(settings, desktop);
+ // Debugging requires pdb loading etc, so we disable live reloading
+ // during a test run with an attached debugger.
+
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ InitializeClassicDesktopLifetime(settings, desktop);
+ }
+ else if (ApplicationLifetime is IActivityApplicationLifetime singleViewFactoryApplicationLifetime)
+ {
+ InitializeActivityLifetime(settings, singleViewFactoryApplicationLifetime);
+ }
+ else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
+ {
+ InitializeSingleViewLifetime(settings, singleViewPlatform);
+ }
}
- else if (ApplicationLifetime is IActivityApplicationLifetime singleViewFactoryApplicationLifetime)
+ else
{
- InitializeActivityLifetime(settings, singleViewFactoryApplicationLifetime);
- }
- else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
- {
- InitializeSingleViewLifetime(settings, singleViewPlatform);
+ // Here, we create a new LiveViewHost, located in the 'Live.Avalonia'
+ // namespace, and pass an ILiveView implementation to it. The ILiveView
+ // implementation should have a parameterless constructor! Next, we
+ // start listening for any changes in the source files. And then, we
+ // show the LiveViewHost window. Simple enough, huh?
+ var window = new LiveViewHost(this, Console.WriteLine);
+ var view = ServiceProvider!.GetRequiredService();
+
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ InitializeClassicDesktopLifetime(window, view, settings, desktop);
+ }
+ else if (ApplicationLifetime is IActivityApplicationLifetime singleViewFactoryApplicationLifetime)
+ {
+ InitializeActivityLifetime(settings, singleViewFactoryApplicationLifetime);
+ }
+ else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
+ {
+ InitializeSingleViewLifetime(settings, singleViewPlatform);
+ }
+
+ window.StartWatchingSourceFilesForHotReloading();
+ Console.WriteLine("LiveViewHost started for hot reloading.");
+ window.Show();
}
- }
#if DEBUG
// Here we subscribe to ReactiveUI default exception handler to avoid app
@@ -89,6 +110,7 @@ public partial class App : Application, ILiveView
//
// In case if you are using another MV* framework, please refer to its
// documentation explaining global exception handling.
+ Console.WriteLine("Debug mode: ReactiveUI default exception handler is set.");
RxApp.DefaultExceptionHandler = Observer.Create(Console.WriteLine);
#endif
@@ -98,12 +120,50 @@ public partial class App : Application, ILiveView
private bool IsProduction()
{
#if DEBUG
- return false;
+ return false;
#else
return true;
#endif
}
+ public async Task PushPageAsync(ViewModelBase viewModel)
+ {
+ var template = DataTemplates.FirstOrDefault(t => t.Match(viewModel));
+ if (template is null)
+ {
+ throw new InvalidOperationException($"No IDataTemplate found for {viewModel.GetType().Name}.");
+ }
+
+ Control? view = template.Build(viewModel) ;
+ if (view is null)
+ {
+ throw new InvalidOperationException(
+ $"Template for {viewModel.GetType().Name} returned .");
+ }
+
+ var page = view as Page;
+ if (page is null)
+ {
+ // NavigationPage expects Page instances. Wrap any fallback control
+ // (e.g. ViewLocator error TextBlock) into a ContentPage so it can render.
+ page = new ContentPage { Content = view };
+ }
+
+ page.DataContext = viewModel;
+
+ MainView? mainView = (Current as App).View;
+
+ // Avoid stacking the same singleton page twice (e.g. SettingsPage).
+ var stack = mainView.NavRoot.NavigationStack;
+ if (stack.Count > 0 && ReferenceEquals(stack[stack.Count - 1], page))
+ {
+ return page;
+ }
+
+ await mainView.NavRoot.PushAsync(page);
+ return page;
+ }
+
public void InitializeSingleViewLifetime(Settings settings, ISingleViewApplicationLifetime singleViewPlatform)
{
singleViewPlatform.MainView = View = ServiceProvider!.GetRequiredService();
@@ -125,37 +185,34 @@ public partial class App : Application, ILiveView
public void InitializeClassicDesktopLifetime(Settings settings, IClassicDesktopStyleApplicationLifetime desktop)
{
var window = ServiceProvider!.GetRequiredService();
- desktop.MainWindow = window;
- this.ConfigureRootView(window.MainView);
+ var mainView = (MainView)CreateView(window);
+ InitializeClassicDesktopLifetime(window, mainView, settings, desktop);
+ }
+ public void InitializeClassicDesktopLifetime(Window window, MainView mainView, Settings settings, IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ this.ConfigureRootView(mainView);
ApplyStettings(settings);
+ desktop.MainWindow = window;
+ window.Content = mainView;
+ window.Show();
}
public void ConfigureRootView(MainView rootView)
-{
- // Déclencher le Boot une seule fois lors du chargement du contrôle à l'écran.
- rootView.AttachedToVisualTree += async (_, _) => await BootOnceAsync();
-
- var sessionStatus = ServiceProvider!.GetRequiredService();
- sessionStatus.LogoutCompleted += () =>
{
- // Remplacer Window.NavRoot par rootView.NavRoot
- rootView.NavRoot.PopToRootAsync();
- };
+ rootView.AttachedToVisualTree += async (_, _) => await BootOnceAsync();
- rootView.SessionBanner.DataContext = sessionStatus;
- this.View = rootView;
-}
-
- private async Task BootOnceAsync()
- {
- if (Interlocked.Exchange(ref _bootStarted, 1) == 1)
+ var sessionStatus = ServiceProvider!.GetRequiredService();
+ sessionStatus.LogoutCompleted += () =>
{
- return;
- }
+ // Remplacer Window.NavRoot par rootView.NavRoot
+ rootView.NavRoot.PopToRootAsync();
+ };
+ if (rootView.DataContext==null)
+ rootView.DataContext = ServiceProvider!.GetRequiredService();
- var api = ServiceProvider!.GetRequiredService();
- await BootAsync(this.ServiceProvider!, api);
+ rootView.SessionBanner.DataContext = sessionStatus;
+ this.View = rootView;
}
///
@@ -177,6 +234,18 @@ public partial class App : Application, ILiveView
settings.DarkMode ? ThemeVariant.Dark : ThemeVariant.Light;
}
+
+ private async Task BootOnceAsync()
+ {
+ if (Interlocked.Exchange(ref _bootStarted, 1) == 1)
+ {
+ return;
+ }
+
+ var api = ServiceProvider!.GetRequiredService();
+ await BootAsync(this.ServiceProvider!, api);
+ }
+
///
/// Run once after the main window is shown: try to refresh the
/// cached OIDC tokens silently; on success, push MainPage on top
@@ -192,9 +261,9 @@ public partial class App : Application, ILiveView
var refreshed = await api.TrySilentLoginAsync().ConfigureAwait(true);
var sessionStatus = provider.GetRequiredService();
sessionStatus.Refresh();
- var homePage = provider.GetRequiredService();
+ var homePageVm = provider.GetRequiredService();
var app = (App)Current!;
- await app.PushPageAsync(homePage);
+ await app.PushPageAsync(homePageVm);
}
///
@@ -250,20 +319,25 @@ public partial class App : Application, ILiveView
await View!.NavRoot.PopAsync();
}
- internal void UseConfigFileWhenLoading(string configFile)
+ internal static void UseConfigFileWhenLoading(string configFile)
{
- this.CliConfigFileSpecification = configFile;
+ CliConfigFileSpecification = configFile;
+ }
+
+ internal static void EnableLiveMode()
+ {
+ LiveMode = true;
}
public object CreateView(Window window)
{
- if (this.ServiceProvider==null)
+ if (this.ServiceProvider == null)
{
var serviceCollection = new ServiceCollection();
this.ServiceProvider = serviceCollection.BuildPostItServices();
}
if (window.DataContext == null)
- window.DataContext = ServiceProvider!.GetRequiredService();
+ window.DataContext = ServiceProvider!.GetRequiredService();
// The AppView class will inherit the DataContext
// of the window. The AppView class can be a
diff --git a/src/PostIt/PostIt/CLIOptions/CLIOptions.cs b/src/PostIt/PostIt/CLIOptions/CLIOptions.cs
index c0bea85ff..643102b38 100644
--- a/src/PostIt/PostIt/CLIOptions/CLIOptions.cs
+++ b/src/PostIt/PostIt/CLIOptions/CLIOptions.cs
@@ -4,15 +4,21 @@ using Mono.Options;
using PostIt;
using PostIt.ViewModels;
+namespace PostIt.CLIOptions;
+
public static class CLIOptionHandler
{
public static void HandleArgs(string[] args)
{
bool showHelp = false;
string configFile = null;
+ bool liveMode = false;
var options = new OptionSet {
{ "c|configuration=", "Configuration file to load", (string v) => configFile = v },
+ { "l|live", "Enable live mode", v => liveMode = true },
+ { "v|version", "Show the application version", v => Console.WriteLine("PostIt version "
+ + typeof(App).Assembly.GetName().Version?.ToString()) },
{ "h|help", "Show this message and exit", v => showHelp = true }
};
@@ -26,8 +32,11 @@ public static class CLIOptionHandler
}
if (configFile != null)
{
- var app = App.Current as App;
- app.UseConfigFileWhenLoading(configFile);
+ App.UseConfigFileWhenLoading(configFile);
+ }
+ if (liveMode)
+ {
+ App.EnableLiveMode();
}
}
}
diff --git a/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs b/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs
index 55dad555b..778f2c4ed 100644
--- a/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs
+++ b/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs
@@ -39,9 +39,12 @@ public static class ServiceCollectionHelpers
var reverseGeocoding = new NominatimReverseGeocodingService();
// Vues
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+
+
+ // Models
services.AddSingleton();
// SettingsPage is a singleton: there must be one and only one
diff --git a/src/PostIt/PostIt/Helpers/ViewModelBaseHelpers.cs b/src/PostIt/PostIt/Helpers/ViewModelBaseHelpers.cs
deleted file mode 100644
index f30e021d9..000000000
--- a/src/PostIt/PostIt/Helpers/ViewModelBaseHelpers.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-using Avalonia.Controls;
-using PostIt.ViewModels;
-
-namespace PostIt.Helpers;
-
-public static class ViewModelBaseHelpers
-{
- public static async Task PushPageAsync(this App app, ViewModelBase vm)
- {
- var window = app.View;
- if (window is null)
- {
- throw new InvalidOperationException("MainWindow is not initialized yet.");
- }
-
- var template = app.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 .");
- }
-
- var page = view as Page;
- if (page is null)
- {
- // NavigationPage expects Page instances. Wrap any fallback control
- // (e.g. ViewLocator error TextBlock) into a ContentPage so it can render.
- page = new ContentPage { Content = view };
- }
-
- 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 page;
- }
-
- await window.NavRoot.PushAsync(page);
-
- return page;
- }
-}
diff --git a/src/PostIt/PostIt/IVMPusherApp.cs b/src/PostIt/PostIt/IVMPusherApp.cs
new file mode 100644
index 000000000..3fc0f474f
--- /dev/null
+++ b/src/PostIt/PostIt/IVMPusherApp.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+using PostIt.ViewModels;
+using Avalonia.Controls;
+
+namespace PostIt;
+
+internal interface IVMPusherApp
+{
+ Task PushPageAsync(ViewModelBase viewModel);
+}
diff --git a/src/PostIt/PostIt/Lang/Resources.Designer.cs b/src/PostIt/PostIt/Lang/Resources.Designer.cs
index 5537f5924..7d600fd2a 100644
--- a/src/PostIt/PostIt/Lang/Resources.Designer.cs
+++ b/src/PostIt/PostIt/Lang/Resources.Designer.cs
@@ -61,7 +61,7 @@ namespace Yavsc.Lang {
}
///
- /// Looks up a localized string similar to Bienvenue dans PostIt!.
+ /// Looks up a localized string similar to Bienvenue dans PostIt !!!.
///
public static string GreetingText {
get {
diff --git a/src/PostIt/PostIt/Lang/Resources.resx b/src/PostIt/PostIt/Lang/Resources.resx
index ae18ff220..574138ef6 100644
--- a/src/PostIt/PostIt/Lang/Resources.resx
+++ b/src/PostIt/PostIt/Lang/Resources.resx
@@ -1,5 +1,5 @@
- Bienvenue dans PostIt!
+ Bienvenue dans PostIt !!!
diff --git a/src/PostIt/PostIt/ViewModels/Layout/HomePageViewModel.cs b/src/PostIt/PostIt/ViewModels/Layout/HomePageViewModel.cs
index 19ed05833..b76f31f27 100644
--- a/src/PostIt/PostIt/ViewModels/Layout/HomePageViewModel.cs
+++ b/src/PostIt/PostIt/ViewModels/Layout/HomePageViewModel.cs
@@ -3,7 +3,6 @@ using System.Threading.Tasks;
using Avalonia;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.DependencyInjection;
-using PostIt.Helpers;
using PostIt.Services;
using PostIt.ViewModels.Chat;
using Yavsc.Api.Client;
diff --git a/src/PostIt/PostIt/ViewModels/Settings/Settings.cs b/src/PostIt/PostIt/ViewModels/Settings/Settings.cs
index e92bf9385..c034437b8 100644
--- a/src/PostIt/PostIt/ViewModels/Settings/Settings.cs
+++ b/src/PostIt/PostIt/ViewModels/Settings/Settings.cs
@@ -271,10 +271,9 @@ public partial class Settings : ViewModelBase
}
var app = App.Current as App;
- if (app!=null)
- if (app.CliConfigFileSpecification!=null)
+ if (App.CliConfigFileSpecification!=null)
{
- SettingsFileFullName = app.CliConfigFileSpecification;
+ SettingsFileFullName = App.CliConfigFileSpecification;
}
if (SettingsFileFullName is not null)
diff --git a/src/PostIt/PostIt/Views/Chat/ChatPage..axaml b/src/PostIt/PostIt/Views/Chat/ChatPage.axaml
similarity index 69%
rename from src/PostIt/PostIt/Views/Chat/ChatPage..axaml
rename to src/PostIt/PostIt/Views/Chat/ChatPage.axaml
index d6b77f2d7..0ac7e818c 100644
--- a/src/PostIt/PostIt/Views/Chat/ChatPage..axaml
+++ b/src/PostIt/PostIt/Views/Chat/ChatPage.axaml
@@ -14,20 +14,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -37,7 +23,7 @@
-
+
@@ -47,7 +33,7 @@
-
+
diff --git a/src/PostIt/PostIt/Views/Layout/HomePage.axaml b/src/PostIt/PostIt/Views/Layout/HomePage.axaml
index 421aa10ee..1de3ece68 100644
--- a/src/PostIt/PostIt/Views/Layout/HomePage.axaml
+++ b/src/PostIt/PostIt/Views/Layout/HomePage.axaml
@@ -16,7 +16,7 @@
FontWeight="SemiBold"
HorizontalAlignment="Center"/>
-
diff --git a/src/PostIt/PostIt/Views/Layout/MainWindow.axaml b/src/PostIt/PostIt/Views/Layout/MainWindow.axaml
index e606cf2b1..821c4da70 100644
--- a/src/PostIt/PostIt/Views/Layout/MainWindow.axaml
+++ b/src/PostIt/PostIt/Views/Layout/MainWindow.axaml
@@ -8,5 +8,5 @@
x:Class="PostIt.Views.MainWindow"
Icon="/Assets/avalonia-logo.ico"
Title="PostIt">
-
+