New startup window.

This commit is contained in:
Burak Kaan Köse
2026-03-05 10:12:03 +01:00
parent d45d3faa89
commit db5ecd60e4
46 changed files with 1857 additions and 234 deletions
+141 -11
View File
@@ -21,14 +21,17 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Calendar;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.Domain.Models.Updates;
using Wino.Mail.Services;
using Wino.Mail.ViewModels;
using Wino.Mail.WinUI.Activation;
using Wino.Mail.WinUI.Interfaces;
using Wino.Mail.WinUI.Models;
using Wino.Mail.WinUI.Services;
using Wino.Messaging.Client.Accounts;
using Wino.Messaging.Client.Navigation;
using Wino.Messaging.Server;
using Wino.Messaging.UI;
using Wino.Services;
@@ -37,13 +40,16 @@ namespace Wino.Mail.WinUI;
public partial class App : WinoApplication,
IRecipient<NewMailSynchronizationRequested>,
IRecipient<NewCalendarSynchronizationRequested>
IRecipient<NewCalendarSynchronizationRequested>,
IRecipient<AccountCreatedMessage>,
IRecipient<GetStartedFromWelcomeRequested>
{
private const int InboxSyncsPerFullSync = 20;
private const string ToggleDefaultModeLaunchArgument = "--mode=toggle-default";
private ISynchronizationManager? _synchronizationManager;
private IPreferencesService? _preferencesService;
private IAccountService? _accountService;
private bool _windowManagerConfigured;
private CancellationTokenSource? _autoSynchronizationLoopCts;
private readonly SemaphoreSlim _autoSynchronizationSemaphore = new(1, 1);
private readonly Dictionary<Guid, int> _inboxSyncCounters = [];
@@ -58,6 +64,52 @@ public partial class App : WinoApplication,
RegisterRecipients();
}
private void EnsureWindowManagerConfigured()
{
if (_windowManagerConfigured)
return;
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
windowManager.ActiveWindowChanged -= OnActiveWindowChanged;
windowManager.ActiveWindowChanged += OnActiveWindowChanged;
windowManager.WindowRemoved -= OnManagedWindowRemoved;
windowManager.WindowRemoved += OnManagedWindowRemoved;
var nativeAppService = Services.GetRequiredService<INativeAppService>();
nativeAppService.GetCoreWindowHwnd = () =>
{
var window = windowManager.ActiveWindow
?? windowManager.GetWindow(WinoWindowKind.Shell)
?? windowManager.GetWindow(WinoWindowKind.Welcome)
?? MainWindow;
return window == null
? IntPtr.Zero
: WinRT.Interop.WindowNative.GetWindowHandle(window);
};
_windowManagerConfigured = true;
}
private void OnActiveWindowChanged(object? sender, WindowEx? window)
{
if (window == null)
return;
MainWindow = window;
InitializeNavigationDispatcher();
}
private void OnManagedWindowRemoved(object? sender, WindowEx window)
{
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
MainWindow = windowManager.ActiveWindow
?? windowManager.GetWindow(WinoWindowKind.Shell)
?? windowManager.GetWindow(WinoWindowKind.Welcome);
InitializeNavigationDispatcher();
}
public bool IsNotificationActivation(out AppNotificationActivatedEventArgs args)
{
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
@@ -93,6 +145,7 @@ public partial class App : WinoApplication,
services.AddTransient(typeof(MailRenderingPageViewModel));
services.AddTransient(typeof(AccountManagementViewModel));
services.AddTransient(typeof(WelcomePageViewModel));
services.AddTransient(typeof(WelcomePageV2ViewModel));
services.AddTransient(typeof(ComposePageViewModel));
services.AddTransient(typeof(IdlePageViewModel));
@@ -153,11 +206,22 @@ public partial class App : WinoApplication,
_synchronizationManager = Services.GetRequiredService<ISynchronizationManager>();
_preferencesService = Services.GetRequiredService<IPreferencesService>();
_accountService = Services.GetRequiredService<IAccountService>();
EnsureWindowManagerConfigured();
var hasAnyAccount = (await _accountService.GetAccountsAsync()).Any();
if (!IsStartupTaskLaunch() && !hasAnyAccount)
{
CreateWelcomeWindow();
await NewThemeService.InitializeAsync();
MainWindow?.Activate();
LogActivation("Welcome window created and activated.");
return;
}
// Check whether the new version requires a migration before starting sync.
var updateManager = Services.GetRequiredService<IUpdateManager>();
var updateNotes = await updateManager.GetLatestUpdateNotesAsync();
bool hasPendingMigration = updateNotes.HasMigrations && updateManager.HasPendingMigrations();
bool hasPendingMigration = updateNotes.HasPendingMigrations && updateManager.HasPendingMigrations();
_preferencesService.PreferenceChanged -= PreferencesServiceChanged;
_preferencesService.PreferenceChanged += PreferencesServiceChanged;
@@ -462,10 +526,19 @@ public partial class App : WinoApplication,
// Initialize theme service after window is created.
await NewThemeService.InitializeAsync();
MainWindow?.Activate();
if (MainWindow != null)
Services.GetRequiredService<IWinoWindowManager>().ActivateWindow(MainWindow);
LogActivation("Window created and activated.");
}
public Task OpenManageAccountsFromWelcomeAsync()
{
Services.GetRequiredService<INavigationService>()
.Navigate(WinoPage.ManageAccountsPage, null, NavigationReferenceFrame.ShellFrame, NavigationTransitionType.DrillIn);
return Task.CompletedTask;
}
/// <summary>
/// Creates the main window without activating it.
/// Used for both normal launch and startup task launch (tray only).
@@ -474,15 +547,15 @@ public partial class App : WinoApplication,
{
LogActivation("Creating main window.");
MainWindow = new ShellWindow();
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
MainWindow = windowManager.CreateWindow(WinoWindowKind.Shell, () => new ShellWindow());
InitializeNavigationDispatcher();
var nativeAppService = Services.GetRequiredService<INativeAppService>();
nativeAppService.GetCoreWindowHwnd = () => WinRT.Interop.WindowNative.GetWindowHandle(MainWindow);
if (MainWindow is not IWinoShellWindow shellWindow)
throw new ArgumentException("MainWindow must implement IWinoShellWindow");
windowManager.SetPrimaryNavigationFrame(WinoWindowKind.Shell, shellWindow.GetMainFrame());
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
if (activationArgs.Kind == ExtendedActivationKind.Launch &&
@@ -508,6 +581,21 @@ public partial class App : WinoApplication,
shellWindow.HandleAppActivation(args?.Arguments, GetCurrentLaunchTileId(), Environment.CommandLine);
}
private void CreateWelcomeWindow()
{
LogActivation("Creating welcome window.");
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
MainWindow = windowManager.CreateWindow(WinoWindowKind.Welcome, () => new WelcomeWindow());
if (MainWindow is WelcomeWindow welcomeWindow)
windowManager.SetPrimaryNavigationFrame(WinoWindowKind.Welcome, welcomeWindow.GetRootFrame());
InitializeNavigationDispatcher();
Services.GetRequiredService<INavigationService>()
.Navigate(WinoPage.WelcomePageV2, null, NavigationReferenceFrame.ShellFrame, NavigationTransitionType.None);
}
private void InitializeNavigationDispatcher()
{
if (MainWindow == null)
@@ -521,18 +609,25 @@ public partial class App : WinoApplication,
private void EnsureMainWindowVisibleAndForeground()
{
if (MainWindow == null)
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
var currentWindow = windowManager.ActiveWindow
?? windowManager.GetWindow(WinoWindowKind.Shell)
?? windowManager.GetWindow(WinoWindowKind.Welcome)
?? MainWindow;
if (currentWindow == null)
return;
MainWindow.Show();
MainWindow.BringToFront();
MainWindow.Activate();
MainWindow = currentWindow;
windowManager.ActivateWindow(currentWindow);
}
private void RegisterRecipients()
{
WeakReferenceMessenger.Default.Register<NewMailSynchronizationRequested>(this);
WeakReferenceMessenger.Default.Register<NewCalendarSynchronizationRequested>(this);
WeakReferenceMessenger.Default.Register<AccountCreatedMessage>(this);
WeakReferenceMessenger.Default.Register<GetStartedFromWelcomeRequested>(this);
}
public async void Receive(NewMailSynchronizationRequested message)
@@ -585,6 +680,41 @@ public partial class App : WinoApplication,
}
}
public void Receive(AccountCreatedMessage message)
{
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
// Only transition when the account was created from the WelcomeWindow.
if (windowManager.GetWindow(WinoWindowKind.Welcome) == null)
return;
MainWindow?.DispatcherQueue?.TryEnqueue(async () =>
{
// Create and activate ShellWindow — ActiveWindowChanged fires and rebinds the dispatcher.
CreateWindow(null);
windowManager.HideWindow(WinoWindowKind.Welcome);
await NewThemeService.ApplyThemeToActiveWindowAsync();
MainWindow?.Activate();
RestartAutoSynchronizationLoop();
});
}
public void Receive(GetStartedFromWelcomeRequested message)
{
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
if (windowManager.GetWindow(WinoWindowKind.Welcome) == null)
return;
MainWindow?.DispatcherQueue?.TryEnqueue(async () =>
{
CreateWindow(null);
windowManager.HideWindow(WinoWindowKind.Welcome);
await NewThemeService.ApplyThemeToActiveWindowAsync();
MainWindow?.Activate();
});
}
private static string GetSynchronizationFailureMessage(MailSynchronizationType synchronizationType, string? exceptionMessage)
{
if (!string.IsNullOrWhiteSpace(exceptionMessage))