From fb8a3d8f904657c0ea82331eb4a047d196d46d3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Fri, 27 Mar 2026 14:45:36 +0100 Subject: [PATCH] Handling some warnings and proper disposals of shells etc. --- AGENTS.md | 1 + .../CalendarAppShellViewModel.cs | 20 + Wino.Mail.ViewModels/MailAppShellViewModel.cs | 41 +- Wino.Mail.WinUI/App.xaml.cs | 20 +- .../AppModeFooterSwitcherControl.xaml | 8 +- .../EditorTabbedCommandBarControl.xaml | 6 +- .../Dialogs/MessageSourceDialog.xaml | 2 +- Wino.Mail.WinUI/Dialogs/MoveMailDialog.xaml | 2 +- Wino.Mail.WinUI/Dialogs/NewAccountDialog.xaml | 2 +- .../Dialogs/SingleCalendarPickerDialog.xaml | 6 +- .../Properties/launchSettings.json | 2 +- Wino.Mail.WinUI/ShellWindow.xaml | 11 - Wino.Mail.WinUI/ShellWindow.xaml.cs | 20 +- .../ViewModels/ContactsShellClient.cs | 16 + .../ViewModels/SettingsShellClient.cs | 12 + .../Views/Account/AccountDetailsPage.xaml | 486 +++++++++--------- .../Calendar/CalendarEventComposePage.xaml | 2 +- .../Views/Settings/AliasManagementPage.xaml | 14 +- .../Settings/SignatureAndEncryptionPage.xaml | 12 +- .../Settings/WinoAccountManagementPage.xaml | 18 +- Wino.Mail.WinUI/Views/WinoAppShell.xaml.cs | 67 ++- 21 files changed, 470 insertions(+), 298 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a4b3166b..5d2615c0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -104,6 +104,7 @@ private string searchQuery = string.Empty; - WinUI 3 auto-converts bool to Visibility: `Visibility="{x:Bind IsVisible, Mode=OneWay}"` - Use XamlHelpers for complex conversions: `{x:Bind helpers:XamlHelpers.ReverseBoolToVisibilityConverter(Prop)}` - `x:Bind` does not implicitly convert `double` to `GridLength`; when binding `RowDefinition.Height` or `ColumnDefinition.Width`, use a `XamlHelpers` method such as `DoubleToGridLength(...)` +- For `ComboBox` controls in XAML, never use `DisplayMemberPath` or `SelectedValuePath`; use a typed `ItemTemplate` and bind `SelectedItem` explicitly, preferably with `x:Bind` ## Localization diff --git a/Wino.Calendar.ViewModels/CalendarAppShellViewModel.cs b/Wino.Calendar.ViewModels/CalendarAppShellViewModel.cs index d43ca51d..480bd580 100644 --- a/Wino.Calendar.ViewModels/CalendarAppShellViewModel.cs +++ b/Wino.Calendar.ViewModels/CalendarAppShellViewModel.cs @@ -198,6 +198,26 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel, _calendarPageViewModel.CleanupForShellDeactivation(); } + public void PrepareForShellShutdown() + { + DetachRuntimeSubscriptions(); + PreferencesService.PreferenceChanged -= PreferencesServiceChanged; + + if (_hasRegisteredPersistentRecipients) + { + UnregisterRecipients(); + _hasRegisteredPersistentRecipients = false; + } + + DateNavigationHeaderItems.Clear(); + SelectedDateNavigationHeaderIndex = -1; + SelectedMenuItemIndex = -1; + MenuItems?.Clear(); + FooterItems?.Clear(); + AccountCalendarStateService.ClearGroupedAccountCalendars(); + _calendarPageViewModel.CleanupForShellDeactivation(); + } + private void AttachRuntimeSubscriptions() { if (_runtimeSubscriptionsAttached) diff --git a/Wino.Mail.ViewModels/MailAppShellViewModel.cs b/Wino.Mail.ViewModels/MailAppShellViewModel.cs index 1f7f6555..7b7c77c9 100644 --- a/Wino.Mail.ViewModels/MailAppShellViewModel.cs +++ b/Wino.Mail.ViewModels/MailAppShellViewModel.cs @@ -30,7 +30,6 @@ namespace Wino.Mail.ViewModels; public partial class MailAppShellViewModel : MailBaseViewModel, IMailShellClient, - IRecipient, IRecipient, IRecipient, IRecipient, @@ -236,14 +235,14 @@ public partial class MailAppShellViewModel : MailBaseViewModel, var activationContext = parameters as ShellModeActivationContext; var shouldRunStartupFlows = activationContext?.IsInitialActivation ?? true; - var hasExistingMenuItems = MenuItems?.Any() == true; + var hasExistingAccountMenuItems = MenuItems?.OfType().Any() == true; PreferencesService.PreferenceChanged -= PreferencesServiceChanged; PreferencesService.PreferenceChanged += PreferencesServiceChanged; await CreateFooterItemsAsync(true); - if (!hasExistingMenuItems) + if (!hasExistingAccountMenuItems) { await RecreateMenuItemsAsync(); } @@ -278,6 +277,24 @@ public partial class MailAppShellViewModel : MailBaseViewModel, PreferencesService.PreferenceChanged -= PreferencesServiceChanged; } + public void PrepareForShellShutdown() + { + PreferencesService.PreferenceChanged -= PreferencesServiceChanged; + + if (_hasRegisteredPersistentRecipients) + { + UnregisterRecipients(); + _hasRegisteredPersistentRecipients = false; + } + + latestSelectedAccountMenuItem = null; + SelectedMenuItem = null; + + MenuItems?.Clear(); + MenuItems?.Add(CreateMailMenuItem); + FooterItems?.Clear(); + } + private async Task ShowWhatIsNewIfNeededAsync() { if (!_updateManager.ShouldShowUpdateNotes()) @@ -1159,7 +1176,6 @@ public partial class MailAppShellViewModel : MailBaseViewModel, Messenger.Register(this); Messenger.Register(this); - Messenger.Register(this); Messenger.Register(this); Messenger.Register(this); Messenger.Register(this); @@ -1177,7 +1193,6 @@ public partial class MailAppShellViewModel : MailBaseViewModel, Messenger.Unregister(this); Messenger.Unregister(this); - Messenger.Unregister(this); Messenger.Unregister(this); Messenger.Unregister(this); Messenger.Unregister(this); @@ -1191,6 +1206,19 @@ public partial class MailAppShellViewModel : MailBaseViewModel, public async void Receive(AccountRemovedMessage message) { + var remainingAccounts = await _accountService.GetAccountsAsync().ConfigureAwait(false); + if (!remainingAccounts.Any()) + { + latestSelectedAccountMenuItem = null; + await ExecuteUIThread(() => + { + SelectedMenuItem = null; + MenuItems?.Clear(); + MenuItems?.Add(CreateMailMenuItem); + }); + return; + } + if (latestSelectedAccountMenuItem?.HoldingAccounts?.Any(a => a.Id == message.Account.Id) == true) { latestSelectedAccountMenuItem = null; @@ -1201,9 +1229,6 @@ public partial class MailAppShellViewModel : MailBaseViewModel, await RestoreSelectedAccountAfterMenuRefreshAsync(false); } - public async void Receive(AccountCreatedMessage message) - => await HandleAccountCreatedAsync(message.Account); - public async Task HandleAccountCreatedAsync(MailAccount createdAccount) { latestSelectedAccountMenuItem = null; diff --git a/Wino.Mail.WinUI/App.xaml.cs b/Wino.Mail.WinUI/App.xaml.cs index 467b9584..dedf4788 100644 --- a/Wino.Mail.WinUI/App.xaml.cs +++ b/Wino.Mail.WinUI/App.xaml.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Toolkit.Uwp.Notifications; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Media.Animation; using Microsoft.Windows.AppLifecycle; using Microsoft.Windows.AppNotifications; using MimeKit.Cryptography; @@ -192,7 +193,7 @@ public partial class App : WinoApplication, if (welcomeWindow == null) return; - windowManager.HideWindow(WinoWindowKind.Shell); + CloseShellWindowIfPresent(); await ActivateWindowAsync(welcomeWindow); } @@ -232,6 +233,16 @@ public partial class App : WinoApplication, welcomeWindow.Close(); } + private void CloseShellWindowIfPresent() + { + var windowManager = Services.GetRequiredService(); + if (windowManager.GetWindow(WinoWindowKind.Shell) is not ShellWindow shellWindow) + return; + + shellWindow.PrepareForClose(); + shellWindow.Close(); + } + private async Task ActivateWindowAsync(WindowEx window) { var windowManager = Services.GetRequiredService(); @@ -792,8 +803,9 @@ public partial class App : WinoApplication, } else { - Services.GetRequiredService() - .Navigate(WinoPage.WelcomeHostPage, null, NavigationReferenceFrame.ShellFrame, NavigationTransitionType.None); + rootFrame.BackStack.Clear(); + rootFrame.ForwardStack.Clear(); + rootFrame.Navigate(typeof(WelcomeHostPage), null, new SuppressNavigationTransitionInfo()); } } @@ -926,8 +938,8 @@ public partial class App : WinoApplication, // All accounts removed — go back to welcome wizard from step 1 Services.GetRequiredService().Reset(); StopAutoSynchronizationLoop(); + CloseShellWindowIfPresent(); CreateWelcomeWindow(); - windowManager.HideWindow(WinoWindowKind.Shell); if (MainWindow != null) await ActivateWindowAsync(MainWindow); }); diff --git a/Wino.Mail.WinUI/Controls/AppModeFooterSwitcherControl.xaml b/Wino.Mail.WinUI/Controls/AppModeFooterSwitcherControl.xaml index 3c1e134d..a738aca8 100644 --- a/Wino.Mail.WinUI/Controls/AppModeFooterSwitcherControl.xaml +++ b/Wino.Mail.WinUI/Controls/AppModeFooterSwitcherControl.xaml @@ -9,22 +9,22 @@ Loaded="ControlLoaded" SelectionChanged="ModeSegmentedControlSelectionChanged" Unloaded="ControlUnloaded"> - + - + - + - + diff --git a/Wino.Mail.WinUI/Controls/EditorTabbedCommandBarControl.xaml b/Wino.Mail.WinUI/Controls/EditorTabbedCommandBarControl.xaml index cee76dd9..7a809963 100644 --- a/Wino.Mail.WinUI/Controls/EditorTabbedCommandBarControl.xaml +++ b/Wino.Mail.WinUI/Controls/EditorTabbedCommandBarControl.xaml @@ -52,6 +52,10 @@ + + + + @@ -231,7 +235,7 @@ x:Name="ParagraphStyleComboBox" Grid.Column="1" MinWidth="104" - DisplayMemberPath="Name" + ItemTemplate="{StaticResource ParagraphStyleOptionTemplate}" PlaceholderText="Paragraph" SelectionChanged="ParagraphStyleComboBox_SelectionChanged" Style="{StaticResource CompactComboBoxStyle}" /> diff --git a/Wino.Mail.WinUI/Dialogs/MessageSourceDialog.xaml b/Wino.Mail.WinUI/Dialogs/MessageSourceDialog.xaml index 3ee0d286..b956804e 100644 --- a/Wino.Mail.WinUI/Dialogs/MessageSourceDialog.xaml +++ b/Wino.Mail.WinUI/Dialogs/MessageSourceDialog.xaml @@ -30,7 +30,7 @@ FontFamily="Consolas" FontSize="12" IsTextSelectionEnabled="True" - Text="{x:Bind MessageSource, Mode=OneWay}" + Text="{x:Bind MessageSource}" TextWrapping="Wrap" /> diff --git a/Wino.Mail.WinUI/Dialogs/MoveMailDialog.xaml b/Wino.Mail.WinUI/Dialogs/MoveMailDialog.xaml index 4555e190..b4fa3bfc 100644 --- a/Wino.Mail.WinUI/Dialogs/MoveMailDialog.xaml +++ b/Wino.Mail.WinUI/Dialogs/MoveMailDialog.xaml @@ -61,7 +61,7 @@ CanDragItems="False" CanReorderItems="False" ItemTemplate="{StaticResource FolderStructureMenuFlyoutItemTemplate}" - ItemsSource="{x:Bind FolderList, Mode=OneWay}" + ItemsSource="{x:Bind FolderList}" SelectedItem="{x:Bind SelectedFolder, Mode=TwoWay}" SelectionMode="Single" /> diff --git a/Wino.Mail.WinUI/Dialogs/NewAccountDialog.xaml b/Wino.Mail.WinUI/Dialogs/NewAccountDialog.xaml index afbb36fa..9eb668bb 100644 --- a/Wino.Mail.WinUI/Dialogs/NewAccountDialog.xaml +++ b/Wino.Mail.WinUI/Dialogs/NewAccountDialog.xaml @@ -163,7 +163,7 @@ diff --git a/Wino.Mail.WinUI/Dialogs/SingleCalendarPickerDialog.xaml b/Wino.Mail.WinUI/Dialogs/SingleCalendarPickerDialog.xaml index 08af00eb..dfb8a745 100644 --- a/Wino.Mail.WinUI/Dialogs/SingleCalendarPickerDialog.xaml +++ b/Wino.Mail.WinUI/Dialogs/SingleCalendarPickerDialog.xaml @@ -28,13 +28,13 @@ @@ -67,7 +67,7 @@ Width="14" Height="14" VerticalAlignment="Center" - Fill="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(BackgroundColorHex), Mode=OneWay}" /> + Fill="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(BackgroundColorHex)}" /> - - - , IRecipient { + private bool _allowClose; public IStatePersistanceService StatePersistanceService { get; } = WinoApplication.Current.Services.GetService() ?? throw new Exception("StatePersistanceService not registered in DI container."); public IPreferencesService PreferencesService { get; } = WinoApplication.Current.Services.GetService() ?? throw new Exception("PreferencesService not registered in DI container."); public INavigationService NavigationService { get; } = WinoApplication.Current.Services.GetService() ?? throw new Exception("NavigationService not registered in DI container."); @@ -354,7 +355,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow, private void OnAppWindowClosing(object sender, Microsoft.UI.Windowing.AppWindowClosingEventArgs e) { - if ((Application.Current as App)?.IsExiting == true) + if (_allowClose || (Application.Current as App)?.IsExiting == true) return; e.Cancel = true; @@ -362,10 +363,27 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow, windowManager?.HideWindow(this); } + public void PrepareForClose() + { + if (MainShellFrame.Content is WinoAppShell shellPage) + { + shellPage.PrepareForWindowClose(); + } + + _allowClose = true; + } + private void OnWindowClosed(object sender, WindowEventArgs e) { + Closed -= OnWindowClosed; AppWindow.Closing -= OnAppWindowClosing; StatePersistanceService.StatePropertyChanged -= StatePersistenceServiceChanged; + + if (MainShellFrame.Content is WinoAppShell shellPage) + { + shellPage.PrepareForWindowClose(); + } + UnregisterRecipients(); } diff --git a/Wino.Mail.WinUI/ViewModels/ContactsShellClient.cs b/Wino.Mail.WinUI/ViewModels/ContactsShellClient.cs index ef73e0c3..fe530331 100644 --- a/Wino.Mail.WinUI/ViewModels/ContactsShellClient.cs +++ b/Wino.Mail.WinUI/ViewModels/ContactsShellClient.cs @@ -34,6 +34,12 @@ public sealed class ContactsShellClient(INavigationService navigationService) : public void Activate(ShellModeActivationContext activationContext) { OnNavigatedTo(NavigationMode.New, activationContext); + + if (MenuItems?.Count == 0) + { + MenuItems.Add(_newContactMenuItem); + } + navigationService.Navigate(WinoPage.ContactsPage, null, NavigationReferenceFrame.InnerShellFrame); } @@ -42,6 +48,16 @@ public sealed class ContactsShellClient(INavigationService navigationService) : OnNavigatedFrom(NavigationMode.New, null!); } + public void PrepareForShellShutdown() + { + SelectedMenuItem = null; + if (MenuItems != null) + { + MenuItems.Clear(); + MenuItems.Add(_newContactMenuItem); + } + } + public Task HandleNavigationItemInvokedAsync(IMenuItem? menuItem) { if (menuItem is NewContactMenuItem) diff --git a/Wino.Mail.WinUI/ViewModels/SettingsShellClient.cs b/Wino.Mail.WinUI/ViewModels/SettingsShellClient.cs index d1fb65b8..45abe10e 100644 --- a/Wino.Mail.WinUI/ViewModels/SettingsShellClient.cs +++ b/Wino.Mail.WinUI/ViewModels/SettingsShellClient.cs @@ -57,6 +57,18 @@ public partial class SettingsShellClient(INavigationService navigationService) : { } + public void PrepareForShellShutdown() + { + if (_hasRegisteredPersistentRecipients) + { + UnregisterRecipients(); + _hasRegisteredPersistentRecipients = false; + } + + SelectedMenuItem = null; + MenuItems?.Clear(); + } + public Task HandleNavigationItemInvokedAsync(IMenuItem? menuItem) { if (menuItem is not SettingsShellPageMenuItem settingsMenuItem) diff --git a/Wino.Mail.WinUI/Views/Account/AccountDetailsPage.xaml b/Wino.Mail.WinUI/Views/Account/AccountDetailsPage.xaml index 19cf2602..ddf3895b 100644 --- a/Wino.Mail.WinUI/Views/Account/AccountDetailsPage.xaml +++ b/Wino.Mail.WinUI/Views/Account/AccountDetailsPage.xaml @@ -52,7 +52,7 @@ HorizontalAlignment="Center" VerticalAlignment="Center" Checked="SyncFolderToggled" - IsChecked="{x:Bind IsSynchronizationEnabled, Mode=OneWay}" + IsChecked="{x:Bind IsSynchronizationEnabled}" Tag="{x:Bind}" Unchecked="SyncFolderToggled" Visibility="{x:Bind IsMoveTarget}" /> @@ -63,7 +63,7 @@ HorizontalAlignment="Center" VerticalAlignment="Center" Checked="UnreadBadgeCheckboxToggled" - IsChecked="{x:Bind ShowUnreadCount, Mode=OneWay}" + IsChecked="{x:Bind ShowUnreadCount}" Tag="{x:Bind}" Unchecked="UnreadBadgeCheckboxToggled" Visibility="{x:Bind IsMoveTarget}" /> @@ -81,7 +81,248 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + +