diff --git a/src/UniGetUI.Avalonia/App.axaml.cs b/src/UniGetUI.Avalonia/App.axaml.cs index 6abd79e4ee..67ec82f6f1 100644 --- a/src/UniGetUI.Avalonia/App.axaml.cs +++ b/src/UniGetUI.Avalonia/App.axaml.cs @@ -54,42 +54,78 @@ public override void OnFrameworkInitializationCompleted() if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - if (OperatingSystem.IsMacOS()) + // Apply the saved theme before any window is shown so the splash + // appears in the user's preferred light/dark variant from the start. + ApplyTheme(CoreSettings.GetValue(CoreSettings.K.PreferredTheme)); + + // Show the splash before any heavy initialization. Skipped in daemon + // mode since the app isn't supposed to be visible at all. + SplashWindow? splash = null; + if (!CoreData.WasDaemon) { - ProcessEnvironmentConfigurator.PrepareForCurrentPlatform(); - using var stream = AssetLoader.Open(new Uri("avares://UniGetUI.Avalonia/Assets/icon.png")); - using var ms = new MemoryStream(); - stream.CopyTo(ms); - MacOsNotificationBridge.SetDockIcon(ms.ToArray()); + splash = new SplashWindow(); + splash.Show(); } - else + + // Defer the rest of startup so the splash gets a chance to paint + // before we block the UI thread loading package managers and the + // main window XAML. Without this the splash window appears empty + // until init completes, defeating its purpose. + Dispatcher.UIThread.Post(() => StartMainWindow(desktop, splash), DispatcherPriority.Background); + } + + base.OnFrameworkInitializationCompleted(); + } + + private static void StartMainWindow(IClassicDesktopStyleApplicationLifetime desktop, SplashWindow? splash) + { + if (OperatingSystem.IsMacOS()) + { + ProcessEnvironmentConfigurator.PrepareForCurrentPlatform(); + using var stream = AssetLoader.Open(new Uri("avares://UniGetUI.Avalonia/Assets/icon.png")); + using var ms = new MemoryStream(); + stream.CopyTo(ms); + MacOsNotificationBridge.SetDockIcon(ms.ToArray()); + } + else + { + ProcessEnvironmentConfigurator.ApplyProxySettingsToProcess(); + } + PEInterface.LoadLoaders(); + var mainWindow = new MainWindow(); + desktop.MainWindow = mainWindow; + AvaloniaAppHost.SecondaryInstanceArgsReceived += args => + HandleSecondaryInstanceArgs(mainWindow, args); + + if (CoreData.WasDaemon) + { + // Start silently: hide the window on first open only. + // Opened fires on every Show() in Avalonia, so we must unsubscribe + // immediately or every ShowFromTray() call would hide the window again. + void HideOnce(object? s, EventArgs e) { - ProcessEnvironmentConfigurator.ApplyProxySettingsToProcess(); + mainWindow.Opened -= HideOnce; + mainWindow.Hide(); } - PEInterface.LoadLoaders(); - ApplyTheme(CoreSettings.GetValue(CoreSettings.K.PreferredTheme)); - var mainWindow = new MainWindow(); - desktop.MainWindow = mainWindow; - AvaloniaAppHost.SecondaryInstanceArgsReceived += args => - HandleSecondaryInstanceArgs(mainWindow, args); + mainWindow.Opened += HideOnce; + } - if (CoreData.WasDaemon) + if (splash is not null) + { + var splashRef = splash; + void CloseSplashOnce(object? s, EventArgs e) { - // Start silently: hide the window on first open only. - // Opened fires on every Show() in Avalonia, so we must unsubscribe - // immediately or every ShowFromTray() call would hide the window again. - void HideOnce(object? s, EventArgs e) - { - mainWindow.Opened -= HideOnce; - mainWindow.Hide(); - } - mainWindow.Opened += HideOnce; + mainWindow.Opened -= CloseSplashOnce; + splashRef.Close(); } - - _ = StartupAsync(mainWindow); + mainWindow.Opened += CloseSplashOnce; } - base.OnFrameworkInitializationCompleted(); + // Framework auto-show already passed (we deferred via Dispatcher.Post), + // so we have to open the window ourselves. + mainWindow.Show(); + + _ = StartupAsync(mainWindow); } private static async Task StartupAsync(MainWindow mainWindow) diff --git a/src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj b/src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj index 2514a337f6..d4a084aa27 100644 --- a/src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj +++ b/src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj @@ -122,6 +122,8 @@ + + @@ -177,6 +179,9 @@ MainWindow.axaml + + SplashWindow.axaml + AbstractPackagesPage.axaml diff --git a/src/UniGetUI.Avalonia/Views/MainWindow.axaml b/src/UniGetUI.Avalonia/Views/MainWindow.axaml index 4044c15d89..1c30bde762 100644 --- a/src/UniGetUI.Avalonia/Views/MainWindow.axaml +++ b/src/UniGetUI.Avalonia/Views/MainWindow.axaml @@ -331,38 +331,46 @@ - - - - + + + + + + diff --git a/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml b/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml index a5ac09b29a..da41f7a75f 100644 --- a/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml +++ b/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml @@ -605,14 +605,17 @@ + HorizontalAlignment="Center" + Padding="0"/> @@ -651,7 +654,9 @@ VerticalAlignment="Top" Background="Transparent" BorderThickness="0" automation:AutomationProperties.Name="{t:Translate More options}"> - + - - + + + + + + + diff --git a/src/UniGetUI.Avalonia/Views/SplashWindow.axaml b/src/UniGetUI.Avalonia/Views/SplashWindow.axaml new file mode 100644 index 0000000000..067b760a61 --- /dev/null +++ b/src/UniGetUI.Avalonia/Views/SplashWindow.axaml @@ -0,0 +1,24 @@ + + + + + diff --git a/src/UniGetUI.Avalonia/Views/SplashWindow.axaml.cs b/src/UniGetUI.Avalonia/Views/SplashWindow.axaml.cs new file mode 100644 index 0000000000..7afc9dabbb --- /dev/null +++ b/src/UniGetUI.Avalonia/Views/SplashWindow.axaml.cs @@ -0,0 +1,22 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using Avalonia.Styling; + +namespace UniGetUI.Avalonia.Views; + +public partial class SplashWindow : Window +{ + public SplashWindow() + { + InitializeComponent(); + + bool isDark = Application.Current?.ActualThemeVariant == ThemeVariant.Dark; + string uri = isDark + ? "avares://UniGetUI.Avalonia/Assets/SplashScreen.theme-dark.png" + : "avares://UniGetUI.Avalonia/Assets/SplashScreen.png"; + SplashImage.Source = new Bitmap(AssetLoader.Open(new Uri(uri))); + } +} diff --git a/src/UniGetUI/Assets/SplashScreen.png b/src/UniGetUI/Assets/SplashScreen.png index cc079c3c6f..d4626ddd0e 100644 Binary files a/src/UniGetUI/Assets/SplashScreen.png and b/src/UniGetUI/Assets/SplashScreen.png differ diff --git a/src/UniGetUI/Assets/SplashScreen.theme-dark.png b/src/UniGetUI/Assets/SplashScreen.theme-dark.png new file mode 100644 index 0000000000..1509b3bfec Binary files /dev/null and b/src/UniGetUI/Assets/SplashScreen.theme-dark.png differ