diff --git a/src/PostIt.Tests/SessionStatusBannerTests.cs b/src/PostIt.Tests/SessionStatusBannerTests.cs
deleted file mode 100644
index d35529db..00000000
--- a/src/PostIt.Tests/SessionStatusBannerTests.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Headless.XUnit;
-using Avalonia.Media;
-using Avalonia.Styling;
-using Avalonia.VisualTree;
-using PostIt.ViewModels;
-using PostIt.Views;
-
-namespace PostIt.Tests;
-
-///
-/// UI tests for . Mounted inside
-/// a real via the headless Avalonia
-/// platform declared in TestApp.cs .
-///
-/// The pattern is the one that UnitTest1.MainPage_Should_Load
-/// established: a test attribute [AvaloniaFact] (from
-/// Avalonia.Headless.XUnit ) instead of plain [Fact] ,
-/// new MainWindow() , window.Show() . The AvaloniaFact
-/// attribute schedules the test body inside a dispatcher, which
-/// is the precondition for the headless Window's
-/// PlatformManager.CreateWindow() to find a registered
-/// service. A plain [Fact] test that calls
-/// new Window().Show() throws because the harness has not
-/// been initialised for that thread.
-///
-/// The session banner's DataContext is not wired in
-/// these tests: App.OnFrameworkInitializationCompleted is
-/// not called in a unit test, so we set the DataContext on the
-/// banner directly. The production code path is exercised
-/// end-to-end by the manual launch, not here.
-///
-public class SessionStatusBannerTests
-{
- [AvaloniaFact]
- public void Banner_renders_three_buttons_in_the_visual_tree()
- {
- var window = new MainWindow();
- window.SessionBanner.DataContext = new SessionStatusViewModel();
- window.Show();
-
- var buttons = window.SessionBanner.GetVisualDescendants()
- .OfType()
- .ToList();
-
- // Three buttons, named by their content text: Se
- // déconnecter, Se connecter, Paramètres. If any one is
- // missing, the user has no way to trigger the
- // corresponding navigation event.
- Assert.Equal(3, buttons.Count);
- Assert.Contains(buttons, b => b.Content as string == "Se déconnecter");
- Assert.Contains(buttons, b => b.Content as string == "Se connecter");
- Assert.Contains(buttons, b => b.Content as string == "Paramètres");
- }
-
- [AvaloniaFact]
- public void Banner_login_button_is_visible_when_logged_out()
- {
- var window = new MainWindow();
- var vm = new SessionStatusViewModel();
- Assert.True(vm.IsLoggedOut); // VM default
- window.SessionBanner.DataContext = vm;
- window.Show();
-
- var login = window.SessionBanner.GetVisualDescendants()
- .OfType()
- .Single(b => b.Content as string == "Se connecter");
-
- // The XAML binds IsVisible to IsLoggedOut. After Show,
- // the binding has been evaluated.
- Assert.True(login.IsVisible);
- }
-
- [AvaloniaFact]
- public void Banner_logout_button_is_hidden_when_logged_out()
- {
- var window = new MainWindow();
- var vm = new SessionStatusViewModel();
- Assert.False(vm.IsLoggedIn); // VM default
- window.SessionBanner.DataContext = vm;
- window.Show();
-
- var logout = window.SessionBanner.GetVisualDescendants()
- .OfType()
- .Single(b => b.Content as string == "Se déconnecter");
-
- Assert.False(logout.IsVisible);
- }
-
- [AvaloniaFact]
- public void Banner_settings_button_is_visible_regardless_of_session()
- {
- var window = new MainWindow();
- window.SessionBanner.DataContext = new SessionStatusViewModel();
- window.Show();
-
- var settings = window.SessionBanner.GetVisualDescendants()
- .OfType()
- .Single(b => b.Content as string == "Paramètres");
-
- // Paramètres is the only button with no IsVisible
- // binding — always shown. The user's only path to the
- // settings page goes through this button.
- Assert.True(settings.IsVisible);
- }
-
- [AvaloniaFact]
- public void Banner_session_label_reflects_DataContext()
- {
- var window = new MainWindow();
- window.SessionBanner.DataContext = new SessionStatusViewModel();
- window.Show();
-
- var label = window.SessionBanner.GetVisualDescendants()
- .OfType()
- .First(t => t.Text == "Déconnecté" || t.Text == "Connecté");
-
- // Default SessionLabel is "Déconnecté" until Refresh()
- // is called with a valid session. This pins the default
- // so a future refactor that breaks the initial value
- // (e.g. by removing the field initialiser) is caught.
- Assert.Equal("Déconnecté", label.Text);
- }
-}
diff --git a/src/PostIt/Directory.Packages.props b/src/PostIt/Directory.Packages.props
index 900f1428..12ed35df 100644
--- a/src/PostIt/Directory.Packages.props
+++ b/src/PostIt/Directory.Packages.props
@@ -12,7 +12,6 @@
-
diff --git a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj
index c543c550..10d48512 100644
--- a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj
+++ b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj
@@ -19,7 +19,6 @@
None
All
-
diff --git a/src/PostIt/PostIt/App.axaml.cs b/src/PostIt/PostIt/App.axaml.cs
index ca7e051d..a73096d2 100644
--- a/src/PostIt/PostIt/App.axaml.cs
+++ b/src/PostIt/PostIt/App.axaml.cs
@@ -5,7 +5,6 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
-using Avalonia.Styling;
using PostIt.Services;
using PostIt.ViewModels;
using PostIt.Views;
@@ -113,24 +112,8 @@ public partial class App : Application
// DataContext, and the TwoWay bindings inside the page keep
// mutating the same in-memory Settings instance that the rest
// of the app reads (OidcClientOptions construction, etc.).
- provider.GetRequiredService().DataContext = settings;
-
- // Settings.DarkMode was previously a dead field: it round-
- // tripped through the settings file and the SettingsPage
- // CheckBox, but no consumer ever read it. Wire it here to
- // Application.RequestedThemeVariant so the toggle takes
- // effect immediately, and seed the initial theme from the
- // value Load() just populated (so a dark-mode user lands on
- // a dark window on first launch, not on a default-light
- // window that flips after the user touches the toggle).
- ApplyDarkMode(settings);
- settings.PropertyChanged += (_, e) =>
- {
- if (e.PropertyName == nameof(Settings.DarkMode))
- {
- ApplyDarkMode(settings);
- }
- };
+ provider.GetRequiredService().DataContext =
+ provider.GetRequiredService();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
@@ -207,12 +190,6 @@ public partial class App : Application
}
}
- private static void ApplyDarkMode(Settings settings)
- {
- Application.Current!.RequestedThemeVariant =
- settings.DarkMode ? ThemeVariant.Dark : ThemeVariant.Light;
- }
-
///
/// Run once after the main window is shown: try to refresh the
/// cached OIDC tokens silently; on success, push MainPage on top
diff --git a/src/PostIt/PostIt/ViewModels/MainPageViewModel.cs b/src/PostIt/PostIt/ViewModels/MainPageViewModel.cs
index a7c70ab3..74996bd9 100644
--- a/src/PostIt/PostIt/ViewModels/MainPageViewModel.cs
+++ b/src/PostIt/PostIt/ViewModels/MainPageViewModel.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
+using Avalonia.Styling;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using PostIt.Models;
@@ -37,6 +38,9 @@ public partial class MainPageViewModel : ViewModelBase
[ObservableProperty]
public partial bool IsBusy { get; set; }
+ [ObservableProperty]
+ ThemeVariant themeVariant = ThemeVariant.Default;
+
[ObservableProperty]
public partial Settings Settings { get; private set; }
diff --git a/src/PostIt/PostIt/Views/SessionStatusBanner.axaml b/src/PostIt/PostIt/Views/SessionStatusBanner.axaml
index 0088bd07..9c6a3f93 100644
--- a/src/PostIt/PostIt/Views/SessionStatusBanner.axaml
+++ b/src/PostIt/PostIt/Views/SessionStatusBanner.axaml
@@ -4,6 +4,7 @@
x:Class="PostIt.Views.SessionStatusBanner"
x:DataType="vm:SessionStatusViewModel">