From 13cb3a1042cc8a548a42cf0cc3f2b0030b99a328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Fri, 14 Nov 2025 12:12:13 +0100 Subject: [PATCH] Account attentions. --- Wino.Authentication/OutlookAuthenticator.cs | 8 +++ Wino.Core.Domain/Constants.cs | 1 + .../Interfaces/INotificationBuilder.cs | 7 +++ .../Interfaces/ISynchronizationManager.cs | 5 -- .../MailSynchronizationResult.cs | 11 ++++- .../Translations/en_US/resources.json | 3 ++ .../Services/NotificationBuilder.cs | 49 ++++++++++++------- Wino.Core/Services/SynchronizationManager.cs | 17 ++----- Wino.Core/Synchronizers/WinoSynchronizer.cs | 4 +- Wino.Mail.WinUI/App.xaml.cs | 4 +- .../Properties/launchSettings.json | 3 +- 11 files changed, 70 insertions(+), 42 deletions(-) diff --git a/Wino.Authentication/OutlookAuthenticator.cs b/Wino.Authentication/OutlookAuthenticator.cs index e06e21a9..12fced75 100644 --- a/Wino.Authentication/OutlookAuthenticator.cs +++ b/Wino.Authentication/OutlookAuthenticator.cs @@ -90,6 +90,8 @@ public class OutlookAuthenticator : BaseAuthenticator, IOutlookAuthenticator try { + throw new MsalUiRequiredException("test", "test2"); + var authResult = await _publicClientApplication.AcquireTokenSilent(Scope, storedAccount).ExecuteAsync(); return new TokenInformationEx(authResult.AccessToken, authResult.Account.Username); @@ -113,6 +115,12 @@ public class OutlookAuthenticator : BaseAuthenticator, IOutlookAuthenticator { await EnsureTokenCacheAttachedAsync(); + // Interactive authentication required but window doesn't exist. + // This can happen when being called from a notification background task and the token is expired. + // Force account attention; + + if (_nativeAppService.GetCoreWindowHwnd == null) throw new AuthenticationAttentionException(account); + AuthenticationResult authResult = await _publicClientApplication .AcquireTokenInteractive(Scope) .ExecuteAsync(); diff --git a/Wino.Core.Domain/Constants.cs b/Wino.Core.Domain/Constants.cs index 0406edcd..fda60349 100644 --- a/Wino.Core.Domain/Constants.cs +++ b/Wino.Core.Domain/Constants.cs @@ -12,6 +12,7 @@ public static class Constants public const string ToastMailUniqueIdKey = nameof(ToastMailUniqueIdKey); public const string ToastActionKey = nameof(ToastActionKey); + public const string ToastMailAccountIdKey = nameof(ToastMailAccountIdKey); public const string ClientLogFile = "Client_.log"; public const string ServerLogFile = "Server_.log"; diff --git a/Wino.Core.Domain/Interfaces/INotificationBuilder.cs b/Wino.Core.Domain/Interfaces/INotificationBuilder.cs index 82d4c21b..dd0af63e 100644 --- a/Wino.Core.Domain/Interfaces/INotificationBuilder.cs +++ b/Wino.Core.Domain/Interfaces/INotificationBuilder.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Wino.Core.Domain.Entities.Mail; +using Wino.Core.Domain.Entities.Shared; namespace Wino.Core.Domain.Interfaces; @@ -22,4 +23,10 @@ public interface INotificationBuilder /// Removes the toast notification for a specific mail by unique id. /// void RemoveNotification(Guid mailUniqueId); + + /// + /// Shows a notification that the account requires attention. + /// + /// Account that needs attention. + void CreateAttentionRequiredNotification(MailAccount account); } diff --git a/Wino.Core.Domain/Interfaces/ISynchronizationManager.cs b/Wino.Core.Domain/Interfaces/ISynchronizationManager.cs index 23f50df3..65df1c23 100644 --- a/Wino.Core.Domain/Interfaces/ISynchronizationManager.cs +++ b/Wino.Core.Domain/Interfaces/ISynchronizationManager.cs @@ -41,11 +41,6 @@ public interface ISynchronizationManager /// bool IsAccountSynchronizing(Guid accountId); - /// - /// Queues a mail action request to the corresponding account's synchronizer. - /// - Task QueueRequestAsync(IRequestBase request, Guid accountId); - /// /// Queues a mail action request to the corresponding account's synchronizer with optional synchronization triggering. /// diff --git a/Wino.Core.Domain/Models/Synchronization/MailSynchronizationResult.cs b/Wino.Core.Domain/Models/Synchronization/MailSynchronizationResult.cs index bdd05b3b..10dec4e4 100644 --- a/Wino.Core.Domain/Models/Synchronization/MailSynchronizationResult.cs +++ b/Wino.Core.Domain/Models/Synchronization/MailSynchronizationResult.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Text.Json.Serialization; using Wino.Core.Domain.Entities.Mail; using Wino.Core.Domain.Enums; @@ -22,6 +23,8 @@ public class MailSynchronizationResult public SynchronizationCompletedState CompletedState { get; set; } + public Exception Exception { get; set; } + public static MailSynchronizationResult Empty => new() { CompletedState = SynchronizationCompletedState.Success }; // Mail synchronization @@ -41,5 +44,9 @@ public class MailSynchronizationResult }; public static MailSynchronizationResult Canceled => new() { CompletedState = SynchronizationCompletedState.Canceled }; - public static MailSynchronizationResult Failed => new() { CompletedState = SynchronizationCompletedState.Failed }; + public static MailSynchronizationResult Failed(Exception exception) => new() + { + CompletedState = SynchronizationCompletedState.Failed, + Exception = exception + }; } diff --git a/Wino.Core.Domain/Translations/en_US/resources.json b/Wino.Core.Domain/Translations/en_US/resources.json index f863df78..6a402f2e 100644 --- a/Wino.Core.Domain/Translations/en_US/resources.json +++ b/Wino.Core.Domain/Translations/en_US/resources.json @@ -47,6 +47,7 @@ "BasicIMAPSetupDialog_Title": "IMAP Account", "Busy": "Busy", "Buttons_AddAccount": "Add Account", + "Buttons_FixAccount": "Fix Account", "Buttons_AddNewAlias": "Add New Alias", "Buttons_Allow": "Allow", "Buttons_ApplyTheme": "Apply Theme", @@ -172,6 +173,8 @@ "ElementTheme_Light": "Light mode", "Emoji": "Emoji", "Error_FailedToSetupSystemFolders_Title": "Failed to setup system folders", + "Exception_AccountNeedsAttention_Title": "Account needs attention", + "Exception_AccountNeedsAttention_Message": "'{0}' requires your attention to continue working.", "Exception_AuthenticationCanceled": "Authentication canceled", "Exception_CustomThemeExists": "This theme already exists.", "Exception_CustomThemeMissingName": "You must provide a name.", diff --git a/Wino.Core.WinUI/Services/NotificationBuilder.cs b/Wino.Core.WinUI/Services/NotificationBuilder.cs index 5c1b0de3..3fe61440 100644 --- a/Wino.Core.WinUI/Services/NotificationBuilder.cs +++ b/Wino.Core.WinUI/Services/NotificationBuilder.cs @@ -9,6 +9,7 @@ using Windows.Data.Xml.Dom; using Windows.UI.Notifications; using Wino.Core.Domain; using Wino.Core.Domain.Entities.Mail; +using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; using Wino.Messaging.UI; @@ -17,19 +18,16 @@ namespace Wino.Core.WinUI.Services; public class NotificationBuilder : INotificationBuilder { - private readonly IUnderlyingThemeService _underlyingThemeService; private readonly IAccountService _accountService; private readonly IFolderService _folderService; private readonly IMailService _mailService; private readonly IThumbnailService _thumbnailService; - public NotificationBuilder(IUnderlyingThemeService underlyingThemeService, - IAccountService accountService, + public NotificationBuilder(IAccountService accountService, IFolderService folderService, IMailService mailService, IThumbnailService thumbnailService) { - _underlyingThemeService = underlyingThemeService; _accountService = accountService; _folderService = folderService; _mailService = mailService; @@ -52,23 +50,23 @@ public class NotificationBuilder : INotificationBuilder { var mailItem = await _mailService.GetSingleMailItemAsync(item.UniqueId); - if (mailItem == null || mailItem.AssignedFolder == null) - continue; + //if (mailItem == null || mailItem.AssignedFolder == null) + // continue; - // Only create notifications for Inbox folder mails - if (mailItem.AssignedFolder.SpecialFolderType != SpecialFolderType.Inbox) - continue; + //// Only create notifications for Inbox folder mails + //if (mailItem.AssignedFolder.SpecialFolderType != SpecialFolderType.Inbox) + // continue; - // Skip folders with synchronization disabled - if (!mailItem.AssignedFolder.IsSynchronizationEnabled) - continue; + //// Skip folders with synchronization disabled + //if (!mailItem.AssignedFolder.IsSynchronizationEnabled) + // continue; - // Skip already read mails - if (mailItem.IsRead) - { - RemoveNotification(mailItem.UniqueId); - continue; - } + //// Skip already read mails + //if (mailItem.IsRead) + //{ + // RemoveNotification(mailItem.UniqueId); + // continue; + //} inboxMailItems.Add(mailItem); } @@ -237,4 +235,19 @@ public class NotificationBuilder : INotificationBuilder Log.Error(ex, $"Failed to remove notification for mail {mailUniqueId}"); } } + + public void CreateAttentionRequiredNotification(MailAccount account) + { + var builder = new ToastContentBuilder(); + builder.SetToastScenario(ToastScenario.Default); + + builder.AddText(Translator.Exception_AccountNeedsAttention_Title); + builder.AddText(string.Format(Translator.Exception_AccountNeedsAttention_Message, account.Name)); + + builder.AddButton(GetDismissButton()); + + builder.AddArgument(Constants.ToastMailAccountIdKey, account.Id.ToString()); + builder.AddButton(new ToastButton().SetContent(Translator.Buttons_FixAccount)); + builder.Show(); + } } diff --git a/Wino.Core/Services/SynchronizationManager.cs b/Wino.Core/Services/SynchronizationManager.cs index 026e266e..dc8f61f0 100644 --- a/Wino.Core/Services/SynchronizationManager.cs +++ b/Wino.Core/Services/SynchronizationManager.cs @@ -134,7 +134,8 @@ public class SynchronizationManager : ISynchronizationManager if (synchronizer == null) { _logger.Error("Could not find or create synchronizer for account {AccountId}", options.AccountId); - return MailSynchronizationResult.Failed; + + return MailSynchronizationResult.Failed(new Exception("Can't create/get synchronizer.")); } _logger.Information("Starting mail synchronization for account {AccountId} with type {SyncType}", @@ -151,12 +152,14 @@ public class SynchronizationManager : ISynchronizationManager if (result.DownloadedMessages?.Any() ?? false) await _notificationBuilder.CreateNotificationsAsync(result.DownloadedMessages); + await _notificationBuilder.UpdateTaskbarIconBadgeAsync(); + return result; } catch (Exception ex) { _logger.Error(ex, "Mail synchronization failed for account {AccountId}", options.AccountId); - return MailSynchronizationResult.Failed; + return MailSynchronizationResult.Failed(ex); } } @@ -178,16 +181,6 @@ public class SynchronizationManager : ISynchronizationManager return false; } - /// - /// Queues a mail action request to the corresponding account's synchronizer. - /// - /// 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. /// diff --git a/Wino.Core/Synchronizers/WinoSynchronizer.cs b/Wino.Core/Synchronizers/WinoSynchronizer.cs index d6aa8b93..611c45ec 100644 --- a/Wino.Core/Synchronizers/WinoSynchronizer.cs +++ b/Wino.Core/Synchronizers/WinoSynchronizer.cs @@ -271,7 +271,7 @@ public abstract class WinoSynchronizer /// Creates the main window and activates it. /// - private async Task CreateAndActivateWindow(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) + private async Task CreateAndActivateWindow(LaunchActivatedEventArgs args) { CreateWindow(args); diff --git a/Wino.Mail.WinUI/Properties/launchSettings.json b/Wino.Mail.WinUI/Properties/launchSettings.json index 8d2cf66c..adba38a2 100644 --- a/Wino.Mail.WinUI/Properties/launchSettings.json +++ b/Wino.Mail.WinUI/Properties/launchSettings.json @@ -1,7 +1,8 @@ { "profiles": { "Wino.Mail.WinUI (Package)": { - "commandName": "MsixPackage" + "commandName": "MsixPackage", + "doNotLaunchApp": true }, "Wino.Mail.WinUI (Unpackaged)": { "commandName": "Project"