diff --git a/Wino.Authentication/OutlookAuthenticator.cs b/Wino.Authentication/OutlookAuthenticator.cs index 10bc5f26..7aa3113a 100644 --- a/Wino.Authentication/OutlookAuthenticator.cs +++ b/Wino.Authentication/OutlookAuthenticator.cs @@ -40,6 +40,7 @@ public class OutlookAuthenticator : BaseAuthenticator, IOutlookAuthenticator ListOperatingSystemAccounts = true, }; + var outlookAppBuilder = PublicClientApplicationBuilder.Create(AuthenticatorConfig.OutlookAuthenticatorClientId) .WithParentActivityOrWindow(nativeAppService.GetCoreWindowHwnd) .WithBroker(options) diff --git a/Wino.Core.Domain/Interfaces/IFolderMenuItem.cs b/Wino.Core.Domain/Interfaces/IFolderMenuItem.cs index 7deff38d..26413208 100644 --- a/Wino.Core.Domain/Interfaces/IFolderMenuItem.cs +++ b/Wino.Core.Domain/Interfaces/IFolderMenuItem.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.ObjectModel; using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Models.Folders; @@ -20,7 +21,7 @@ public interface IBaseFolderMenuItem : IMenuItem int UnreadItemCount { get; set; } SpecialFolderType SpecialFolderType { get; } IEnumerable HandlingFolders { get; } - IEnumerable SubMenuItems { get; } + ObservableCollection SubMenuItems { get; } bool IsMoveTarget { get; } bool IsSticky { get; } bool IsSystemFolder { get; } diff --git a/Wino.Core.Domain/Interfaces/ISynchronizationManager.cs b/Wino.Core.Domain/Interfaces/ISynchronizationManager.cs index c1ee65c6..0bcdb657 100644 --- a/Wino.Core.Domain/Interfaces/ISynchronizationManager.cs +++ b/Wino.Core.Domain/Interfaces/ISynchronizationManager.cs @@ -45,6 +45,11 @@ public interface ISynchronizationManager /// Task QueueRequestAsync(IRequestBase request, Guid accountId); + /// + /// Queues a mail action request to the corresponding account's synchronizer with optional synchronization triggering. + /// + Task QueueRequestAsync(IRequestBase request, Guid accountId, bool triggerSynchronization); + /// /// Handles folder synchronization for the given account. /// diff --git a/Wino.Core.Domain/MenuItems/FolderMenuItem.cs b/Wino.Core.Domain/MenuItems/FolderMenuItem.cs index c5cbbf5d..d3f0fbc4 100644 --- a/Wino.Core.Domain/MenuItems/FolderMenuItem.cs +++ b/Wino.Core.Domain/MenuItems/FolderMenuItem.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using CommunityToolkit.Mvvm.ComponentModel; using Wino.Core.Domain.Entities.Shared; @@ -20,6 +21,8 @@ public partial class FolderMenuItem : MenuItemBase Parameter.IsSticky; public bool IsSystemFolder => Parameter.IsSystemFolder; + + /// /// Display name of the folder. More and Category folders have localized display names. /// @@ -53,7 +56,7 @@ public partial class FolderMenuItem : MenuItemBase Parameter.ShowUnreadCount; - IEnumerable IBaseFolderMenuItem.SubMenuItems => SubMenuItems; + public new ObservableCollection SubMenuItems { get; set; } = new ObservableCollection(); public FolderMenuItem(IMailItemFolder folderStructure, MailAccount parentAccount, IMenuItem parentMenuItem) : base(folderStructure, folderStructure.Id, parentMenuItem) { diff --git a/Wino.Core.WinUI/Services/DialogServiceBase.cs b/Wino.Core.WinUI/Services/DialogServiceBase.cs index 9e917f72..8d90f1f4 100644 --- a/Wino.Core.WinUI/Services/DialogServiceBase.cs +++ b/Wino.Core.WinUI/Services/DialogServiceBase.cs @@ -37,6 +37,11 @@ public class DialogServiceBase : IDialogServiceBase ApplicationResourceManager = applicationResourceManager; } + protected XamlRoot GetXamlRoot() + { + return WinoApplication.MainWindow?.Content?.XamlRoot; + } + public async Task PickFilePathAsync(string saveFileName) { var picker = new FolderPicker() @@ -122,7 +127,8 @@ public class DialogServiceBase : IDialogServiceBase { return new AccountCreationDialog { - RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme() + RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme(), + XamlRoot = GetXamlRoot() }; } @@ -195,6 +201,8 @@ public class DialogServiceBase : IDialogServiceBase try { + dialog.XamlRoot = GetXamlRoot(); + return await dialog.ShowAsync(); } catch (Exception ex) diff --git a/Wino.Core.WinUI/Services/NativeAppService.cs b/Wino.Core.WinUI/Services/NativeAppService.cs index b05ca9a3..59e7606d 100644 --- a/Wino.Core.WinUI/Services/NativeAppService.cs +++ b/Wino.Core.WinUI/Services/NativeAppService.cs @@ -1,10 +1,8 @@ using System; using System.Threading.Tasks; using Windows.ApplicationModel; -using Windows.Foundation.Metadata; using Windows.Storage; using Windows.System; -using Windows.UI.Shell; using Wino.Core.Domain.Interfaces; @@ -88,20 +86,21 @@ public class NativeAppService : INativeAppService return string.Format("{0}.{1}.{2}.{3}", version.Major, version.Minor, version.Build, version.Revision); } + [Obsolete("Not supported for Win SDK")] public async Task PinAppToTaskbarAsync() { // If Start screen manager API's aren't present - if (!ApiInformation.IsTypePresent("Windows.UI.Shell.TaskbarManager")) return; + //if (!ApiInformation.IsTypePresent("Windows.UI.Shell.TaskbarManager")) return; - // Get the taskbar manager - var taskbarManager = TaskbarManager.GetDefault(); + //// Get the taskbar manager + //var taskbarManager = TaskbarManager.GetDefault(); - // If Taskbar doesn't allow pinning, don't show the tip - if (!taskbarManager.IsPinningAllowed) return; + //// If Taskbar doesn't allow pinning, don't show the tip + //if (!taskbarManager.IsPinningAllowed) return; - // If already pinned, don't show the tip - if (await taskbarManager.IsCurrentAppPinnedAsync()) return; + //// If already pinned, don't show the tip + //if (await taskbarManager.IsCurrentAppPinnedAsync()) return; - await taskbarManager.RequestPinCurrentAppAsync(); + //await taskbarManager.RequestPinCurrentAppAsync(); } } diff --git a/Wino.Core.WinUI/Services/NewThemeService.cs b/Wino.Core.WinUI/Services/NewThemeService.cs index 340a61f3..b019b1db 100644 --- a/Wino.Core.WinUI/Services/NewThemeService.cs +++ b/Wino.Core.WinUI/Services/NewThemeService.cs @@ -198,8 +198,10 @@ public class NewThemeService : INewThemeService await ApplyCustomThemeAsync(true); // Registering to color changes, thus we notice when user changes theme system wide - uiSettings.ColorValuesChanged -= UISettingsColorChanged; - uiSettings.ColorValuesChanged += UISettingsColorChanged; + + // TODO: WinUI: This event seems to be very unreliable. It causes a crash when the function runs under. + //uiSettings.ColorValuesChanged -= UISettingsColorChanged; + //uiSettings.ColorValuesChanged += UISettingsColorChanged; isInitialized = true; } diff --git a/Wino.Core.WinUI/Services/PreferencesService.cs b/Wino.Core.WinUI/Services/PreferencesService.cs index 215daacc..14557fa6 100644 --- a/Wino.Core.WinUI/Services/PreferencesService.cs +++ b/Wino.Core.WinUI/Services/PreferencesService.cs @@ -227,7 +227,7 @@ public class PreferencesService(IConfigurationService configurationService) : Ob public bool IsNavigationPaneOpened { get => _configurationService.Get(nameof(IsNavigationPaneOpened), true); - set => SaveProperty(propertyName: nameof(IsNavigationPaneOpened), value); + set => SetPropertyAndSave(propertyName: nameof(IsNavigationPaneOpened), value); } public bool AutoSelectNextItem diff --git a/Wino.Core/Services/SynchronizationManager.cs b/Wino.Core/Services/SynchronizationManager.cs index 2f8f453e..3c50c939 100644 --- a/Wino.Core/Services/SynchronizationManager.cs +++ b/Wino.Core/Services/SynchronizationManager.cs @@ -194,6 +194,17 @@ public class SynchronizationManager : ISynchronizationManager /// Request to queue /// Account ID to queue the request for public async Task QueueRequestAsync(IRequestBase request, Guid accountId) + { + await QueueRequestAsync(request, accountId, triggerSynchronization: true); + } + + /// + /// Queues a mail action request to the corresponding account's synchronizer with optional synchronization triggering. + /// + /// Request to queue + /// Account ID to queue the request for + /// Whether to automatically trigger synchronization after queuing the request + public async Task QueueRequestAsync(IRequestBase request, Guid accountId, bool triggerSynchronization) { EnsureInitialized(); @@ -208,6 +219,32 @@ public class SynchronizationManager : ISynchronizationManager request.GetType().Name, accountId); synchronizer.QueueRequest(request); + + if (triggerSynchronization) + { + // Trigger synchronization to execute the queued request + _logger.Debug("Triggering synchronization to execute queued request for account {AccountId}", accountId); + + var synchronizationOptions = new MailSynchronizationOptions() + { + AccountId = accountId, + Type = MailSynchronizationType.ExecuteRequests + }; + + // Trigger synchronization asynchronously without waiting for completion + // This matches the pattern used in WinoRequestDelegator + _ = Task.Run(async () => + { + try + { + await SynchronizeMailAsync(synchronizationOptions); + } + catch (Exception ex) + { + _logger.Error(ex, "Failed to execute synchronization after queuing request for account {AccountId}", accountId); + } + }); + } } /// diff --git a/Wino.Core/Services/WinoRequestDelegator.cs b/Wino.Core/Services/WinoRequestDelegator.cs index 7686a9c9..cd3c31a7 100644 --- a/Wino.Core/Services/WinoRequestDelegator.cs +++ b/Wino.Core/Services/WinoRequestDelegator.cs @@ -135,7 +135,8 @@ public class WinoRequestDelegator : IWinoRequestDelegator private async Task QueueRequestAsync(IRequestBase request, Guid accountId) { - await SynchronizationManager.Instance.QueueRequestAsync(request, accountId); + // Don't trigger synchronization for individual requests - we'll trigger it once for all requests + await SynchronizationManager.Instance.QueueRequestAsync(request, accountId, triggerSynchronization: false); } private Task QueueSynchronizationAsync(Guid accountId) diff --git a/Wino.Mail.WinUI/App.xaml.cs b/Wino.Mail.WinUI/App.xaml.cs index a2d97c06..bddf800b 100644 --- a/Wino.Mail.WinUI/App.xaml.cs +++ b/Wino.Mail.WinUI/App.xaml.cs @@ -83,18 +83,21 @@ public partial class App : WinoApplication, IRecipient(); - var configService = Services.GetService(); + var newThemeService = Services.GetRequiredService(); + var configService = Services.GetRequiredService(); + var nativeAppService = Services.GetRequiredService(); // Load saved backdrop type before creating window var savedBackdropType = (WindowBackdropType)configService.Get("WindowBackdropTypeKey", (int)WindowBackdropType.Mica); MainWindow = new ShellWindow(); + nativeAppService.GetCoreWindowHwnd = () => WinRT.Interop.WindowNative.GetWindowHandle(MainWindow); + await InitializeServicesAsync(); // Initialize system tray - var systemTrayService = Services.GetService(); + var systemTrayService = Services.GetRequiredService(); if (systemTrayService != null) { systemTrayService.Initialize(); diff --git a/Wino.Mail.WinUI/Services/DialogService.cs b/Wino.Mail.WinUI/Services/DialogService.cs index 5ca49e69..8ad6ac92 100644 --- a/Wino.Mail.WinUI/Services/DialogService.cs +++ b/Wino.Mail.WinUI/Services/DialogService.cs @@ -37,9 +37,11 @@ public class DialogService : DialogServiceBase, IMailDialogService { if (accountCreationDialogResult.ProviderType == MailProviderType.IMAP4) { + return new NewImapSetupDialog { - RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme() + RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme(), + XamlRoot = GetXamlRoot() }; } else diff --git a/Wino.Mail.WinUI/ShellWindow.xaml b/Wino.Mail.WinUI/ShellWindow.xaml index 5cdb6a86..858a2253 100644 --- a/Wino.Mail.WinUI/ShellWindow.xaml +++ b/Wino.Mail.WinUI/ShellWindow.xaml @@ -21,6 +21,7 @@ - +