From e1f53c7f9f69adb9200e95d809370cd61d96308d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Thu, 16 Apr 2026 13:46:52 +0200 Subject: [PATCH] Add account context menu actions --- Wino.Core.Domain/Enums/MailOperation.cs | 1 + Wino.Core.Domain/Interfaces/IShellClient.cs | 1 + .../Translations/en_US/resources.json | 5 +- .../Helpers/SynchronizationActionHelper.cs | 1 + .../Folder/CreateRootFolderRequest.cs | 11 ++++ Wino.Core/Services/WinoRequestDelegator.cs | 6 ++ Wino.Core/Synchronizers/GmailSynchronizer.cs | 11 ++++ Wino.Core/Synchronizers/ImapSynchronizer.cs | 9 +++ .../Synchronizers/OutlookSynchronizer.cs | 11 ++++ Wino.Core/Synchronizers/WinoSynchronizer.cs | 4 ++ Wino.Mail.ViewModels/MailAppShellViewModel.cs | 24 ++++++++ Wino.Mail.WinUI/Views/WinoAppShell.xaml | 1 + Wino.Mail.WinUI/Views/WinoAppShell.xaml.cs | 61 +++++++++++++++++++ 13 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 Wino.Core/Requests/Folder/CreateRootFolderRequest.cs diff --git a/Wino.Core.Domain/Enums/MailOperation.cs b/Wino.Core.Domain/Enums/MailOperation.cs index ea2773f3..b67af1b3 100644 --- a/Wino.Core.Domain/Enums/MailOperation.cs +++ b/Wino.Core.Domain/Enums/MailOperation.cs @@ -23,6 +23,7 @@ public enum FolderSynchronizerOperation MarkFolderRead, DeleteFolder, CreateSubFolder, + CreateRootFolder, } public enum CalendarSynchronizerOperation diff --git a/Wino.Core.Domain/Interfaces/IShellClient.cs b/Wino.Core.Domain/Interfaces/IShellClient.cs index ab9ad0c9..80343b06 100644 --- a/Wino.Core.Domain/Interfaces/IShellClient.cs +++ b/Wino.Core.Domain/Interfaces/IShellClient.cs @@ -41,6 +41,7 @@ public interface IMailShellClient : IShellClient Task ChangeLoadedAccountAsync(IAccountMenuItem clickedBaseAccountMenuItem, bool navigateInbox = true); Task PerformFolderOperationAsync(FolderOperation operation, IBaseFolderMenuItem folderMenuItem); Task PerformMoveOperationAsync(IEnumerable items, IBaseFolderMenuItem targetFolderMenuItem); + Task CreateRootFolderAsync(IAccountMenuItem accountMenuItem); Task CreateNewMailForAsync(MailAccount account); } diff --git a/Wino.Core.Domain/Translations/en_US/resources.json b/Wino.Core.Domain/Translations/en_US/resources.json index 7fe57093..8a23af75 100644 --- a/Wino.Core.Domain/Translations/en_US/resources.json +++ b/Wino.Core.Domain/Translations/en_US/resources.json @@ -315,6 +315,7 @@ "DialogMessage_PrintingSuccessMessage": "Mail is sent to printer.", "DialogMessage_PrintingSuccessTitle": "Success", "DialogMessage_RenameFolderMessage": "Enter new name for this folder", + "DialogMessage_CreateFolderMessage": "Enter name for the new folder", "DialogMessage_RenameFolderTitle": "Rename Folder", "DialogMessage_RenameLinkedAccountsMessage": "Enter new name for linked account", "DialogMessage_RenameLinkedAccountsTitle": "Rename Linked Account", @@ -1534,5 +1535,7 @@ "AccountSetup_GoBackButton": "Go Back", "AccountSetup_TryAgainButton": "Try Again", "Exception_FailedToSynchronizeCategories": "Failed to synchronize categories", - "ImapCalDavSettings_AutoDiscoveryFailed": "Auto-discovery failed. Please enter settings manually in the Advanced tab." + "ImapCalDavSettings_AutoDiscoveryFailed": "Auto-discovery failed. Please enter settings manually in the Advanced tab.", + "AccountContextMenu_ManageAccountSettings": "Manage account settings", + "AccountContextMenu_CreateFolder": "Create folder" } diff --git a/Wino.Core/Helpers/SynchronizationActionHelper.cs b/Wino.Core/Helpers/SynchronizationActionHelper.cs index 072a44dd..c5ccda6f 100644 --- a/Wino.Core/Helpers/SynchronizationActionHelper.cs +++ b/Wino.Core/Helpers/SynchronizationActionHelper.cs @@ -124,6 +124,7 @@ public static class SynchronizationActionHelper MarkFolderAsReadRequest => Translator.SyncAction_MarkingFolderAsRead, DeleteFolderRequest => Translator.FolderOperation_Delete, CreateSubFolderRequest => Translator.FolderOperation_CreateSubFolder, + CreateRootFolderRequest => Translator.AccountContextMenu_CreateFolder, _ => null }; } diff --git a/Wino.Core/Requests/Folder/CreateRootFolderRequest.cs b/Wino.Core/Requests/Folder/CreateRootFolderRequest.cs new file mode 100644 index 00000000..f9636fc6 --- /dev/null +++ b/Wino.Core/Requests/Folder/CreateRootFolderRequest.cs @@ -0,0 +1,11 @@ +using Wino.Core.Domain.Entities.Mail; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Models.Requests; + +namespace Wino.Core.Requests.Folder; + +public record CreateRootFolderRequest(MailItemFolder Folder, string NewFolderName) : FolderRequestBase(Folder, FolderSynchronizerOperation.CreateRootFolder) +{ + public override void ApplyUIChanges() { } + public override void RevertUIChanges() { } +} diff --git a/Wino.Core/Services/WinoRequestDelegator.cs b/Wino.Core/Services/WinoRequestDelegator.cs index 2b9d6e9d..bbeb692a 100644 --- a/Wino.Core/Services/WinoRequestDelegator.cs +++ b/Wino.Core/Services/WinoRequestDelegator.cs @@ -15,6 +15,7 @@ using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.Synchronization; using Wino.Core.Helpers; using Wino.Core.Requests.Calendar; +using Wino.Core.Requests.Folder; using Wino.Core.Requests.Mail; using Wino.Messaging.Server; using Wino.Messaging.UI; @@ -220,6 +221,11 @@ public class WinoRequestDelegator : IWinoRequestDelegator await SendSyncActionsAddedAsync(requestList, accountId).ConfigureAwait(false); await QueueSynchronizationAsync(accountId).ConfigureAwait(false); + + if (requestList.Any(r => r is DeleteFolderRequest or CreateSubFolderRequest or CreateRootFolderRequest)) + { + await QueueFoldersOnlySynchronizationAsync(accountId).ConfigureAwait(false); + } } private async Task CreateCalendarEventRequestAsync(CalendarOperationPreparationRequest calendarPreparationRequest) diff --git a/Wino.Core/Synchronizers/GmailSynchronizer.cs b/Wino.Core/Synchronizers/GmailSynchronizer.cs index c62e3b90..f6636dea 100644 --- a/Wino.Core/Synchronizers/GmailSynchronizer.cs +++ b/Wino.Core/Synchronizers/GmailSynchronizer.cs @@ -1724,6 +1724,17 @@ public class GmailSynchronizer : WinoSynchronizer(networkCall, request, request)]; } + public override List> CreateRootFolder(CreateRootFolderRequest request) + { + var label = new Label() + { + Name = request.NewFolderName + }; + + var networkCall = _gmailService.Users.Labels.Create(label, "me"); + return [new HttpRequestBundle(networkCall, request, request)]; + } + #endregion #region Request Execution diff --git a/Wino.Core/Synchronizers/ImapSynchronizer.cs b/Wino.Core/Synchronizers/ImapSynchronizer.cs index b888f88a..8c62edbe 100644 --- a/Wino.Core/Synchronizers/ImapSynchronizer.cs +++ b/Wino.Core/Synchronizers/ImapSynchronizer.cs @@ -317,6 +317,15 @@ public class ImapSynchronizer : WinoSynchronizer> CreateRootFolder(CreateRootFolderRequest request) + { + return CreateSingleTaskBundle(async (client, item) => + { + var rootFolder = client.GetFolder(client.PersonalNamespaces[0]); + await rootFolder.CreateAsync(request.NewFolderName, true).ConfigureAwait(false); + }, request, request); + } + public override List> CreateCalendarEvent(CreateCalendarEventRequest request) { var handler = ResolveCalendarOperationHandler(); diff --git a/Wino.Core/Synchronizers/OutlookSynchronizer.cs b/Wino.Core/Synchronizers/OutlookSynchronizer.cs index 3dca7a54..e7740ceb 100644 --- a/Wino.Core/Synchronizers/OutlookSynchronizer.cs +++ b/Wino.Core/Synchronizers/OutlookSynchronizer.cs @@ -2003,6 +2003,17 @@ public class OutlookSynchronizer : WinoSynchronizer(networkCall, request)]; } + public override List> CreateRootFolder(CreateRootFolderRequest request) + { + var requestBody = new MailFolder + { + DisplayName = request.NewFolderName + }; + + var networkCall = _graphClient.Me.MailFolders.ToPostRequestInformation(requestBody); + return [new HttpRequestBundle(networkCall, request)]; + } + #endregion public override async Task ExecuteNativeRequestsAsync(List> batchedRequests, CancellationToken cancellationToken = default) diff --git a/Wino.Core/Synchronizers/WinoSynchronizer.cs b/Wino.Core/Synchronizers/WinoSynchronizer.cs index 1ed4fcfa..43132b20 100644 --- a/Wino.Core/Synchronizers/WinoSynchronizer.cs +++ b/Wino.Core/Synchronizers/WinoSynchronizer.cs @@ -222,6 +222,9 @@ public abstract class WinoSynchronizer> MarkFolderAsRead(MarkFolderAsReadRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); public virtual List> DeleteFolder(DeleteFolderRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); public virtual List> CreateSubFolder(CreateSubFolderRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual List> CreateRootFolder(CreateRootFolderRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); public virtual List> UpdateCategories(BatchMailCategoryAssignmentRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); public virtual List> CreateCategory(MailCategoryCreateRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); public virtual List> UpdateCategory(MailCategoryUpdateRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); diff --git a/Wino.Mail.ViewModels/MailAppShellViewModel.cs b/Wino.Mail.ViewModels/MailAppShellViewModel.cs index c93578dc..686a2195 100644 --- a/Wino.Mail.ViewModels/MailAppShellViewModel.cs +++ b/Wino.Mail.ViewModels/MailAppShellViewModel.cs @@ -21,6 +21,7 @@ using Wino.Core.Domain.Models.Launch; using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.Navigation; using Wino.Core.Domain.Models.Synchronization; +using Wino.Core.Requests.Folder; using Wino.Core.Services; using Wino.Mail.ViewModels.Data; using Wino.Messaging.Client.Accounts; @@ -624,6 +625,29 @@ public partial class MailAppShellViewModel : MailBaseViewModel, } } + public async Task CreateRootFolderAsync(IAccountMenuItem accountMenuItem) + { + var account = accountMenuItem?.HoldingAccounts?.FirstOrDefault(); + if (account == null) + return; + + var folderName = await _dialogService.ShowTextInputDialogAsync( + string.Empty, + Translator.AccountContextMenu_CreateFolder, + Translator.DialogMessage_CreateFolderMessage, + Translator.Buttons_Create); + + if (string.IsNullOrWhiteSpace(folderName)) + return; + + var placeholderFolder = new MailItemFolder + { + MailAccountId = account.Id + }; + + await _winoRequestDelegator.ExecuteAsync(account.Id, [new CreateRootFolderRequest(placeholderFolder, folderName.Trim())]); + } + public Task HandleAccountAttentionAsync(MailAccount account) => FixAccountIssuesAsync(account); diff --git a/Wino.Mail.WinUI/Views/WinoAppShell.xaml b/Wino.Mail.WinUI/Views/WinoAppShell.xaml index d20c551c..10c1bee7 100644 --- a/Wino.Mail.WinUI/Views/WinoAppShell.xaml +++ b/Wino.Mail.WinUI/Views/WinoAppShell.xaml @@ -31,6 +31,7 @@ NavigateToAccountSettings(accountMenuItem); + flyout.Items.Add(manageAccountSettingsItem); + + var createFolderItem = new MenuFlyoutItem + { + Text = Translator.AccountContextMenu_CreateFolder + }; + createFolderItem.Icon = new WinoFontIcon { Icon = WinoIconGlyph.CreateFolder }; + createFolderItem.Click += async (_, _) => await ViewModel.MailClient.CreateRootFolderAsync(accountMenuItem); + flyout.Items.Add(createFolderItem); + + flyout.ShowAt(accountNavigationItem, new FlyoutShowOptions + { + ShowMode = FlyoutShowMode.Standard, + Position = new Point(p.X + 30, p.Y - 20) + }); + } + + private void NavigateToAccountSettings(AccountMenuItem accountMenuItem) + { + ViewModel.NavigationService.ChangeApplicationMode( + WinoApplicationMode.Settings, + new ShellModeActivationContext + { + Parameter = WinoPage.ManageAccountsPage, + SuppressStartupFlows = true + }); + + _ = DispatcherQueue.EnqueueAsync(() => + { + WeakReferenceMessenger.Default.Send(new SettingsRootNavigationRequested(WinoPage.ManageAccountsPage)); + WeakReferenceMessenger.Default.Send(new BreadcrumbNavigationRequested( + accountMenuItem.AccountName, + WinoPage.AccountDetailsPage, + accountMenuItem.AccountId)); + }); + } + public void Receive(CreateNewMailWithMultipleAccountsRequested message) { if (!ViewModel.IsMailMode)