From 536fbb23a1820ce57b7c380f3d61d05f1dbf1799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Tue, 9 Jul 2024 01:05:16 +0200 Subject: [PATCH] Folder operations, Gmail folder sync improvements and rework of menu items. (#273) * New rename folder dialog keys. * Insfra work for folder operations and rename folder code. * RenameFolder for Gmail. * Fixed input dialog to take custom take for primary button. * Missing rename for DS call. * Outlook to throw exception in case of error. * Implemented rename folder functionality for Outlook. * Remove default primary text from input dialog. * Fixed an issue where outlook folder rename does not work. * Disable vertical scroll for composing page editor items. * Fixing some issues with imap folder sync. * fix copy pasta * TODO folder update/removed overrides for shell. * New rename folder dialog keys. * Insfra work for folder operations and rename folder code. * RenameFolder for Gmail. * Fixed input dialog to take custom take for primary button. * Missing rename for DS call. * Outlook to throw exception in case of error. * Implemented rename folder functionality for Outlook. * Remove default primary text from input dialog. * Fixed an issue where outlook folder rename does not work. * Disable vertical scroll for composing page editor items. * Fixing some issues with imap folder sync. * fix copy pasta * TODO folder update/removed overrides for shell. * New rename folder dialog keys. * Insfra work for folder operations and rename folder code. * RenameFolder for Gmail. * Fixed input dialog to take custom take for primary button. * Missing rename for DS call. * Outlook to throw exception in case of error. * Implemented rename folder functionality for Outlook. * Remove default primary text from input dialog. * Fixed an issue where outlook folder rename does not work. * Disable vertical scroll for composing page editor items. * Fixing some issues with imap folder sync. * fix copy pasta * TODO folder update/removed overrides for shell. * New rename folder dialog keys. * Fixed an issue where redundant older updates causing pivots to be re-created. * New empty folder request * New rename folder dialog keys. * Insfra work for folder operations and rename folder code. * RenameFolder for Gmail. * Fixed input dialog to take custom take for primary button. * Missing rename for DS call. * Outlook to throw exception in case of error. * Implemented rename folder functionality for Outlook. * Remove default primary text from input dialog. * Fixed an issue where outlook folder rename does not work. * Fixing some issues with imap folder sync. * fix copy pasta * TODO folder update/removed overrides for shell. * New rename folder dialog keys. * New rename folder dialog keys. * New rename folder dialog keys. * Fixed an issue where redundant older updates causing pivots to be re-created. * New empty folder request * Enable empty folder on base sync. * Move updates on event listeners. * Remove folder UI messages. * Reworked folder synchronization for gmail. * Loading folders on the fly as the selected account changed instead of relying on cached menu items. * Merged account folder items, re-navigating to existing rendering page. * - Reworked merged account menu system. - Reworked unread item count loadings. - Fixed back button visibility. - Instant rendering of mails if renderer is active. - Animation fixes. - Menu item re-load crash/hang fixes. * Handle folder renaming on the UI. * Empty folder for all synchronizers. * New execution delay mechanism and handling folder mark as read for all synchronizers. * Revert UI changes on failure for IMAP. * Remove duplicate translation keys. * Cleanup. --- Wino.Core.Domain/Entities/MailItemFolder.cs | 3 + Wino.Core.Domain/Enums/MailOperation.cs | 4 +- .../Interfaces/IAccountMenuItem.cs | 8 + Wino.Core.Domain/Interfaces/IDialogService.cs | 2 +- .../Interfaces/IFolderMenuItem.cs | 1 + Wino.Core.Domain/Interfaces/IFolderService.cs | 29 +- Wino.Core.Domain/Interfaces/IMailService.cs | 16 +- Wino.Core.Domain/Interfaces/IRequestBundle.cs | 9 + .../Interfaces/IWinoRequestDelegator.cs | 6 +- .../Interfaces/IWinoRequestProcessor.cs | 10 +- .../Models/Accounts/UnreadItemCountResult.cs | 13 + .../FolderOperationPreperationRequest.cs | 12 + .../MailOperationPreperationRequest.cs | 54 +- .../Navigation/NavigationTransitionType.cs | 2 +- .../Models/Requests/RequestBase.cs | 6 + .../Translations/en_US/resources.json | 2 + Wino.Core.Domain/Translator.Designer.cs | 535 +----------------- Wino.Core/Extensions/FolderTreeExtensions.cs | 27 +- .../Extensions/GoogleIntegratorExtensions.cs | 136 ++++- Wino.Core/Integration/BaseMailIntegrator.cs | 7 + .../Processors/DefaultChangeProcessor.cs | 39 +- .../Processors/ImapChangeProcessor.cs | 10 +- Wino.Core/MenuItems/AccountMenuItem.cs | 8 +- Wino.Core/MenuItems/FolderMenuItem.cs | 2 + Wino.Core/MenuItems/MenuItemCollection.cs | 185 +++--- .../MenuItems/MergedAccountFolderMenuItem.cs | 2 + Wino.Core/MenuItems/MergedAccountMenuItem.cs | 36 +- .../Mails/DisposeRenderingFrameRequested.cs | 7 + Wino.Core/Requests/EmptyFolderRequest.cs | 31 + Wino.Core/Requests/MarkFolderAsReadRequest.cs | 37 ++ Wino.Core/Requests/RenameFolderRequest.cs | 11 +- Wino.Core/Requests/SendDraftRequest.cs | 2 + Wino.Core/Requests/UIMessages.cs | 8 +- Wino.Core/Services/FolderService.cs | 300 ++++++---- Wino.Core/Services/MailService.cs | 25 +- Wino.Core/Services/WinoRequestDelegator.cs | 18 +- Wino.Core/Services/WinoRequestProcessor.cs | 46 +- Wino.Core/Synchronizers/BaseSynchronizer.cs | 35 +- Wino.Core/Synchronizers/GmailSynchronizer.cs | 181 +++--- Wino.Core/Synchronizers/ImapSynchronizer.cs | 71 ++- .../Synchronizers/OutlookSynchronizer.cs | 57 +- Wino.Core/Wino.Core.csproj | 2 +- .../AccountManagementViewModel.cs | 2 +- Wino.Mail.ViewModels/AppShellViewModel.cs | 511 ++++++++--------- Wino.Mail.ViewModels/BaseViewModel.cs | 20 +- Wino.Mail.ViewModels/MailListPageViewModel.cs | 54 +- .../MailRenderingPageViewModel.cs | 19 +- .../MergedAccountDetailsPageViewModel.cs | 3 +- .../NewMailItemRenderingRequestedEvent.cs | 18 + Wino.Mail/App.xaml | 9 + Wino.Mail/AppShell.xaml | 98 +--- Wino.Mail/AppShell.xaml.cs | 22 +- Wino.Mail/Controls/AccountNavigationItem.cs | 33 +- .../Controls/Advanced/WinoAppTitleBar.xaml | 2 +- .../Controls/Advanced/WinoAppTitleBar.xaml.cs | 5 +- Wino.Mail/Dialogs/TextInputDialog.xaml | 1 - Wino.Mail/Dialogs/TextInputDialog.xaml.cs | 5 + .../NavigationMenuTemplateSelector.cs | 3 +- Wino.Mail/Services/DialogService.cs | 3 +- Wino.Mail/Services/WinoNavigationService.cs | 18 +- Wino.Mail/Styles/ItemContainerStyles.xaml | 133 +++-- .../Views/Account/AccountManagementPage.xaml | 2 +- Wino.Mail/Views/MailListPage.xaml | 2 +- Wino.Mail/Views/MailListPage.xaml.cs | 8 +- Wino.Mail/Views/MailRenderingPage.xaml.cs | 2 +- Wino.Mail/Views/NewAccountManagementPage.xaml | 6 +- Wino.Mail/Views/SettingsPage.xaml | 7 +- 67 files changed, 1396 insertions(+), 1585 deletions(-) create mode 100644 Wino.Core.Domain/Models/Accounts/UnreadItemCountResult.cs create mode 100644 Wino.Core.Domain/Models/Folders/FolderOperationPreperationRequest.cs create mode 100644 Wino.Core/Messages/Mails/DisposeRenderingFrameRequested.cs create mode 100644 Wino.Core/Requests/EmptyFolderRequest.cs create mode 100644 Wino.Core/Requests/MarkFolderAsReadRequest.cs create mode 100644 Wino.Mail.ViewModels/Messages/NewMailItemRenderingRequestedEvent.cs diff --git a/Wino.Core.Domain/Entities/MailItemFolder.cs b/Wino.Core.Domain/Entities/MailItemFolder.cs index d33fbb15..30a24ca6 100644 --- a/Wino.Core.Domain/Entities/MailItemFolder.cs +++ b/Wino.Core.Domain/Entities/MailItemFolder.cs @@ -67,6 +67,9 @@ namespace Wino.Core.Domain.Entities return false; } + public static MailItemFolder CreateMoreFolder() => new MailItemFolder() { IsSticky = true, SpecialFolderType = SpecialFolderType.More, FolderName = Translator.MoreFolderNameOverride }; + public static MailItemFolder CreateCategoriesFolder() => new MailItemFolder() { IsSticky = true, SpecialFolderType = SpecialFolderType.Category, FolderName = Translator.CategoriesFolderNameOverride }; + public override string ToString() => FolderName; } } diff --git a/Wino.Core.Domain/Enums/MailOperation.cs b/Wino.Core.Domain/Enums/MailOperation.cs index b7a20b6c..0d9962cd 100644 --- a/Wino.Core.Domain/Enums/MailOperation.cs +++ b/Wino.Core.Domain/Enums/MailOperation.cs @@ -11,8 +11,10 @@ ChangeFlag, AlwaysMoveTo, MoveToFocused, + Archive, RenameFolder, - Archive + EmptyFolder, + MarkFolderRead, } // UI requests diff --git a/Wino.Core.Domain/Interfaces/IAccountMenuItem.cs b/Wino.Core.Domain/Interfaces/IAccountMenuItem.cs index b3870e3e..23fce0c1 100644 --- a/Wino.Core.Domain/Interfaces/IAccountMenuItem.cs +++ b/Wino.Core.Domain/Interfaces/IAccountMenuItem.cs @@ -5,9 +5,17 @@ namespace Wino.Core.Domain.Interfaces { public interface IAccountMenuItem : IMenuItem { + bool IsEnabled { get; set; } double SynchronizationProgress { get; set; } int UnreadItemCount { get; set; } IEnumerable HoldingAccounts { get; } void UpdateAccount(MailAccount account); } + + public interface IMergedAccountMenuItem : IAccountMenuItem + { + int MergedAccountCount { get; } + + MergedInbox Parameter { get; } + } } diff --git a/Wino.Core.Domain/Interfaces/IDialogService.cs b/Wino.Core.Domain/Interfaces/IDialogService.cs index 8d5d152f..a2986bd9 100644 --- a/Wino.Core.Domain/Interfaces/IDialogService.cs +++ b/Wino.Core.Domain/Interfaces/IDialogService.cs @@ -29,7 +29,7 @@ namespace Wino.Core.Domain.Interfaces Task ShowMoveMailFolderDialogAsync(List availableFolders); Task ShowNewAccountMailProviderDialogAsync(List availableProviders); IAccountCreationDialog GetAccountCreationDialog(MailProviderType type); - Task ShowTextInputDialogAsync(string currentInput, string dialogTitle, string dialogDescription); + Task ShowTextInputDialogAsync(string currentInput, string dialogTitle, string dialogDescription, string primaryButtonText); Task ShowEditAccountDialogAsync(MailAccount account); Task ShowAccountPickerDialogAsync(List availableAccounts); diff --git a/Wino.Core.Domain/Interfaces/IFolderMenuItem.cs b/Wino.Core.Domain/Interfaces/IFolderMenuItem.cs index 756243ab..2fe58504 100644 --- a/Wino.Core.Domain/Interfaces/IFolderMenuItem.cs +++ b/Wino.Core.Domain/Interfaces/IFolderMenuItem.cs @@ -20,6 +20,7 @@ namespace Wino.Core.Domain.Interfaces int UnreadItemCount { get; set; } SpecialFolderType SpecialFolderType { get; } IEnumerable HandlingFolders { get; } + IEnumerable SubMenuItems { get; } bool IsMoveTarget { get; } bool IsSticky { get; } bool IsSystemFolder { get; } diff --git a/Wino.Core.Domain/Interfaces/IFolderService.cs b/Wino.Core.Domain/Interfaces/IFolderService.cs index 3a1c597e..3c4e0d26 100644 --- a/Wino.Core.Domain/Interfaces/IFolderService.cs +++ b/Wino.Core.Domain/Interfaces/IFolderService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Wino.Core.Domain.Entities; using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Models.Accounts; using Wino.Core.Domain.Models.Folders; using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.Synchronization; @@ -15,13 +16,10 @@ namespace Wino.Core.Domain.Interfaces Task GetFolderAsync(Guid folderId); Task GetFolderAsync(Guid accountId, string remoteFolderId); Task> GetFoldersAsync(Guid accountId); - Task> GetUnreadUpdateFoldersAsync(Guid accountId); - Task SetSpecialFolderAsync(Guid folderId, SpecialFolderType type); Task GetSpecialFolderByAccountIdAsync(Guid accountId, SpecialFolderType type); Task GetCurrentItemCountForFolder(Guid folderId); Task GetFolderNotificationBadgeAsync(Guid folderId); Task ChangeStickyStatusAsync(Guid folderId, bool isSticky); - Task UpdateCustomServerMailListAsync(Guid accountId, List folders); Task UpdateSystemFolderConfigurationAsync(Guid accountId, SystemFolderConfiguration configuration); Task ChangeFolderSynchronizationStateAsync(Guid folderId, bool isSynchronizationEnabled); @@ -39,16 +37,6 @@ namespace Wino.Core.Domain.Interfaces /// Task> GetMailFolderPairMetadatasAsync(string mailCopyId); - // v2 - - /// - /// Performs bulk update for the given folders. - /// Used in Gmail. - /// - /// Account that folders belong to. - /// Folders to update. - Task BulkUpdateFolderStructureAsync(Guid accountId, List allFolders); - /// /// Deletes the folder for the given account by remote folder id. /// @@ -84,12 +72,23 @@ namespace Wino.Core.Domain.Interfaces /// Folder to update. Task UpdateFolderLastSyncDateAsync(Guid folderId); - Task TestAsync(); - /// /// Updates the given folder. /// /// Folder to update. Task UpdateFolderAsync(MailItemFolder folder); + + /// + /// Returns the active folder menu items for the given account for UI. + /// + /// Account to get folder menu items for. + Task> GetAccountFoldersForDisplayAsync(IAccountMenuItem accountMenuItem); + + /// + /// Returns a list of unread item counts for the given account ids. + /// Every folder that is marked as show unread badge is included. + /// + /// Account ids to get unread folder counts for. + Task> GetUnreadItemCountResultsAsync(IEnumerable accountIds); } } diff --git a/Wino.Core.Domain/Interfaces/IMailService.cs b/Wino.Core.Domain/Interfaces/IMailService.cs index 6c1be462..108c1b3e 100644 --- a/Wino.Core.Domain/Interfaces/IMailService.cs +++ b/Wino.Core.Domain/Interfaces/IMailService.cs @@ -14,10 +14,6 @@ namespace Wino.Core.Domain.Interfaces Task CreateDraftAsync(MailAccount composerAccount, MimeMessage generatedReplyMime, MimeMessage replyingMimeMessage = null, IMailItem replyingMailItem = null); Task> FetchMailsAsync(MailListInitializationOptions options); - Task> GetMailIdsByFolderIdAsync(Guid folderId); - - // v2 - /// /// Deletes all mail copies for all folders. /// @@ -84,5 +80,17 @@ namespace Wino.Core.Domain.Interfaces /// /// Native mail id of the message. Task IsMailExistsAsync(string mailCopyId); + + /// + /// Returns all mails for given folder id. + /// + /// Folder id to get mails for + Task> GetMailsByFolderIdAsync(Guid folderId); + + /// + /// Returns all unread mails for given folder id. + /// + /// Folder id to get unread mails for. + Task> GetUnreadMailsByFolderIdAsync(Guid folderId); } } diff --git a/Wino.Core.Domain/Interfaces/IRequestBundle.cs b/Wino.Core.Domain/Interfaces/IRequestBundle.cs index dca28129..7c81cbd9 100644 --- a/Wino.Core.Domain/Interfaces/IRequestBundle.cs +++ b/Wino.Core.Domain/Interfaces/IRequestBundle.cs @@ -40,6 +40,15 @@ namespace Wino.Core.Domain.Interfaces /// Reverts the UI changes applied by if the request fails. /// void RevertUIChanges(); + + /// + /// Whether synchronizations should be delayed after executing this request. + /// Specially Outlook sometimes don't report changes back immidiately after sending the API request. + /// This results following synchronization to miss the changes. + /// We add small delay for the following synchronization after executing current requests to overcome this issue. + /// Default is false. + /// + bool DelayExecution { get; } } public interface IRequest : IRequestBase diff --git a/Wino.Core.Domain/Interfaces/IWinoRequestDelegator.cs b/Wino.Core.Domain/Interfaces/IWinoRequestDelegator.cs index 9782e4dd..19906904 100644 --- a/Wino.Core.Domain/Interfaces/IWinoRequestDelegator.cs +++ b/Wino.Core.Domain/Interfaces/IWinoRequestDelegator.cs @@ -1,5 +1,4 @@ using System.Threading.Tasks; -using Wino.Core.Domain.Enums; using Wino.Core.Domain.Models.Folders; using Wino.Core.Domain.Models.MailItem; @@ -28,8 +27,7 @@ namespace Wino.Core.Domain.Interfaces /// /// Prepares requires IRequest collection for folder actions and executes them via proper synchronizers. /// - /// Folder operation to execute. - /// Target folder - Task ExecuteAsync(FolderOperation operation, IMailItemFolder folderStructure); + /// Folder prep request. + Task ExecuteAsync(FolderOperationPreperationRequest folderOperationPreperationRequest); } } diff --git a/Wino.Core.Domain/Interfaces/IWinoRequestProcessor.cs b/Wino.Core.Domain/Interfaces/IWinoRequestProcessor.cs index c35b4e9c..32b1aacf 100644 --- a/Wino.Core.Domain/Interfaces/IWinoRequestProcessor.cs +++ b/Wino.Core.Domain/Interfaces/IWinoRequestProcessor.cs @@ -1,15 +1,18 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Wino.Core.Domain.Enums; using Wino.Core.Domain.Models.Folders; using Wino.Core.Domain.Models.MailItem; -using Wino.Core.Domain.Models.Requests; namespace Wino.Core.Domain.Interfaces { public interface IWinoRequestProcessor { - Task PrepareFolderRequestAsync(FolderOperation operation, IMailItemFolder mailItemFolder); + /// + /// Prepares proper folder action requests for synchronizers to execute. + /// + /// + /// Base request that synchronizer can execute. + Task PrepareFolderRequestAsync(FolderOperationPreperationRequest request); /// /// Prepares proper Wino requests for synchronizers to execute categorized by AccountId and FolderId. @@ -17,6 +20,7 @@ namespace Wino.Core.Domain.Interfaces /// User action /// Selected mails. /// When required folder target is not available for account. + /// Base request that synchronizer can execute. Task> PrepareRequestsAsync(MailOperationPreperationRequest request); } } diff --git a/Wino.Core.Domain/Models/Accounts/UnreadItemCountResult.cs b/Wino.Core.Domain/Models/Accounts/UnreadItemCountResult.cs new file mode 100644 index 00000000..ba5aabf5 --- /dev/null +++ b/Wino.Core.Domain/Models/Accounts/UnreadItemCountResult.cs @@ -0,0 +1,13 @@ +using System; +using Wino.Core.Domain.Enums; + +namespace Wino.Core.Domain.Models.Accounts +{ + public class UnreadItemCountResult + { + public Guid FolderId { get; set; } + public Guid AccountId { get; set; } + public SpecialFolderType SpecialFolderType { get; set; } + public int UnreadItemCount { get; set; } + } +} diff --git a/Wino.Core.Domain/Models/Folders/FolderOperationPreperationRequest.cs b/Wino.Core.Domain/Models/Folders/FolderOperationPreperationRequest.cs new file mode 100644 index 00000000..af3ccf7e --- /dev/null +++ b/Wino.Core.Domain/Models/Folders/FolderOperationPreperationRequest.cs @@ -0,0 +1,12 @@ +using Wino.Core.Domain.Entities; +using Wino.Core.Domain.Enums; + +namespace Wino.Core.Domain.Models.Folders +{ + /// + /// Encapsulates a request to prepare a folder operation like Rename, Delete, etc. + /// + /// Folder operation. + /// Target folder. + public record FolderOperationPreperationRequest(FolderOperation Action, MailItemFolder Folder) { } +} diff --git a/Wino.Core.Domain/Models/MailItem/MailOperationPreperationRequest.cs b/Wino.Core.Domain/Models/MailItem/MailOperationPreperationRequest.cs index a12ac267..1a1758a1 100644 --- a/Wino.Core.Domain/Models/MailItem/MailOperationPreperationRequest.cs +++ b/Wino.Core.Domain/Models/MailItem/MailOperationPreperationRequest.cs @@ -9,61 +9,31 @@ namespace Wino.Core.Domain.Models.MailItem /// /// Encapsulates the options for preparing requests to execute mail operations for mail items like Move, Delete, MarkAsRead, etc. /// - public class MailOperationPreperationRequest + /// Action to execute. + /// Mail copies execute the action on. + /// Whether the operation can be reverted if needed. + /// eg. MarkAsRead on already read item will set the action to MarkAsUnread. + /// This is used in hover actions for example. + /// Whether hard delete protection should be ignored. + /// Discard draft requests for example should ignore hard delete protection. + /// Moving folder for the Move operation. + /// If null and the action is Move, the user will be prompted to select a folder. + public record MailOperationPreperationRequest(MailOperation Action, IEnumerable MailItems, bool ToggleExecution, bool IgnoreHardDeleteProtection, IMailItemFolder MoveTargetFolder) { public MailOperationPreperationRequest(MailOperation action, IEnumerable mailItems, bool toggleExecution = false, IMailItemFolder moveTargetFolder = null, - bool ignoreHardDeleteProtection = false) + bool ignoreHardDeleteProtection = false) : this(action, mailItems ?? throw new ArgumentNullException(nameof(mailItems)), toggleExecution, ignoreHardDeleteProtection, moveTargetFolder) { - Action = action; - MailItems = mailItems ?? throw new ArgumentNullException(nameof(mailItems)); - ToggleExecution = toggleExecution; - MoveTargetFolder = moveTargetFolder; - IgnoreHardDeleteProtection = ignoreHardDeleteProtection; } public MailOperationPreperationRequest(MailOperation action, MailCopy singleMailItem, bool toggleExecution = false, IMailItemFolder moveTargetFolder = null, - bool ignoreHardDeleteProtection = false) + bool ignoreHardDeleteProtection = false) : this(action, new List() { singleMailItem }, toggleExecution, ignoreHardDeleteProtection, moveTargetFolder) { - Action = action; - MailItems = new List() { singleMailItem }; - ToggleExecution = toggleExecution; - MoveTargetFolder = moveTargetFolder; - IgnoreHardDeleteProtection = ignoreHardDeleteProtection; } - - /// - /// Action to execute. - /// - public MailOperation Action { get; set; } - - /// - /// Mail copies execute the action on. - /// - public IEnumerable MailItems { get; set; } - - /// - /// Whether the operation can be reverted if needed. - /// eg. MarkAsRead on already read item will set the action to MarkAsUnread. - /// This is used in hover actions for example. - /// - public bool ToggleExecution { get; set; } - - /// - /// Whether hard delete protection should be ignored. - /// Discard draft requests for example should ignore hard delete protection. - /// - public bool IgnoreHardDeleteProtection { get; set; } - - /// - /// Moving folder for the Move operation. - /// If null and the action is Move, the user will be prompted to select a folder. - /// - public IMailItemFolder MoveTargetFolder { get; } } } diff --git a/Wino.Core.Domain/Models/Navigation/NavigationTransitionType.cs b/Wino.Core.Domain/Models/Navigation/NavigationTransitionType.cs index fba63741..9a78c0fa 100644 --- a/Wino.Core.Domain/Models/Navigation/NavigationTransitionType.cs +++ b/Wino.Core.Domain/Models/Navigation/NavigationTransitionType.cs @@ -3,6 +3,6 @@ public enum NavigationTransitionType { None, // Supress - DrillIn, + DrillIn } } diff --git a/Wino.Core.Domain/Models/Requests/RequestBase.cs b/Wino.Core.Domain/Models/Requests/RequestBase.cs index 8aa3459b..bb2281cb 100644 --- a/Wino.Core.Domain/Models/Requests/RequestBase.cs +++ b/Wino.Core.Domain/Models/Requests/RequestBase.cs @@ -11,17 +11,23 @@ namespace Wino.Core.Domain.Models.Requests public abstract IBatchChangeRequest CreateBatch(IEnumerable requests); public abstract void ApplyUIChanges(); public abstract void RevertUIChanges(); + + public virtual bool DelayExecution => false; } public abstract record FolderRequestBase(MailItemFolder Folder, MailSynchronizerOperation Operation) : IFolderRequest { public abstract void ApplyUIChanges(); public abstract void RevertUIChanges(); + + public virtual bool DelayExecution => false; } public abstract record BatchRequestBase(IEnumerable Items, MailSynchronizerOperation Operation) : IBatchChangeRequest { public abstract void ApplyUIChanges(); public abstract void RevertUIChanges(); + + public virtual bool DelayExecution => false; } } diff --git a/Wino.Core.Domain/Translations/en_US/resources.json b/Wino.Core.Domain/Translations/en_US/resources.json index 6e096be9..01c35517 100644 --- a/Wino.Core.Domain/Translations/en_US/resources.json +++ b/Wino.Core.Domain/Translations/en_US/resources.json @@ -84,6 +84,8 @@ "DialogMessage_UnlinkAccountsConfirmationTitle": "Unlink Accounts", "DialogMessage_EmptySubjectConfirmation": "Missin Subject", "DialogMessage_EmptySubjectConfirmationMessage": "Message has no subject. Do you want to continue?", + "DialogMessage_RenameFolderTitle": "Rename Folder", + "DialogMessage_RenameFolderMessage": "Enter new name for this folder", "DialogMessage_UnsubscribeConfirmationTitle": "Unsubscribe", "DialogMessage_UnsubscribeConfirmationOneClickMessage": "Do you want to stop getting messages from {0}?", "DialogMessage_UnsubscribeConfirmationGoToWebsiteMessage": "To stop getting messages from {0}, go to their website to unsubscribe.", diff --git a/Wino.Core.Domain/Translator.Designer.cs b/Wino.Core.Domain/Translator.Designer.cs index cca15f79..359c41c8 100644 --- a/Wino.Core.Domain/Translator.Designer.cs +++ b/Wino.Core.Domain/Translator.Designer.cs @@ -1,5 +1,4 @@  - namespace Wino.Core.Domain { public class Translator @@ -19,3131 +18,2619 @@ namespace Wino.Core.Domain } } - /// /// all done /// public static string AccountCreationDialog_Completed => Resources.GetTranslatedString(@"AccountCreationDialog_Completed"); - /// /// initializing /// public static string AccountCreationDialog_Initializing => Resources.GetTranslatedString(@"AccountCreationDialog_Initializing"); - /// /// We are getting folder information at the moment. /// public static string AccountCreationDialog_PreparingFolders => Resources.GetTranslatedString(@"AccountCreationDialog_PreparingFolders"); - /// /// Account information is being saved. /// public static string AccountCreationDialog_SigninIn => Resources.GetTranslatedString(@"AccountCreationDialog_SigninIn"); - /// /// Account Name /// public static string AccountEditDialog_Message => Resources.GetTranslatedString(@"AccountEditDialog_Message"); - /// /// Edit Account /// public static string AccountEditDialog_Title => Resources.GetTranslatedString(@"AccountEditDialog_Title"); - /// /// Pick an account /// public static string AccountPickerDialog_Title => Resources.GetTranslatedString(@"AccountPickerDialog_Title"); - /// /// Sender Display Name /// public static string AccountSettingsDialog_AccountName => Resources.GetTranslatedString(@"AccountSettingsDialog_AccountName"); - /// /// eg. John Doe /// public static string AccountSettingsDialog_AccountNamePlaceholder => Resources.GetTranslatedString(@"AccountSettingsDialog_AccountNamePlaceholder"); - /// /// Add /// public static string AddHyperlink => Resources.GetTranslatedString(@"AddHyperlink"); - /// /// Searching for mail settings... /// public static string AutoDiscoveryProgressMessage => Resources.GetTranslatedString(@"AutoDiscoveryProgressMessage"); - /// /// Advanced Configuration /// public static string BasicIMAPSetupDialog_AdvancedConfiguration => Resources.GetTranslatedString(@"BasicIMAPSetupDialog_AdvancedConfiguration"); - /// /// Your credentials will only be stored locally on your computer. /// public static string BasicIMAPSetupDialog_CredentialLocalMessage => Resources.GetTranslatedString(@"BasicIMAPSetupDialog_CredentialLocalMessage"); - /// /// Some accounts require additional steps to sign in /// public static string BasicIMAPSetupDialog_Description => Resources.GetTranslatedString(@"BasicIMAPSetupDialog_Description"); - /// /// Display Name /// public static string BasicIMAPSetupDialog_DisplayName => Resources.GetTranslatedString(@"BasicIMAPSetupDialog_DisplayName"); - /// /// eg. John Doe /// public static string BasicIMAPSetupDialog_DisplayNamePlaceholder => Resources.GetTranslatedString(@"BasicIMAPSetupDialog_DisplayNamePlaceholder"); - /// /// Learn more /// public static string BasicIMAPSetupDialog_LearnMore => Resources.GetTranslatedString(@"BasicIMAPSetupDialog_LearnMore"); - /// /// E-Mail Address /// public static string BasicIMAPSetupDialog_MailAddress => Resources.GetTranslatedString(@"BasicIMAPSetupDialog_MailAddress"); - /// /// johndoe@fabrikam.com /// public static string BasicIMAPSetupDialog_MailAddressPlaceholder => Resources.GetTranslatedString(@"BasicIMAPSetupDialog_MailAddressPlaceholder"); - /// /// Password /// public static string BasicIMAPSetupDialog_Password => Resources.GetTranslatedString(@"BasicIMAPSetupDialog_Password"); - /// /// IMAP Account /// public static string BasicIMAPSetupDialog_Title => Resources.GetTranslatedString(@"BasicIMAPSetupDialog_Title"); - /// /// Add Account /// public static string Buttons_AddAccount => Resources.GetTranslatedString(@"Buttons_AddAccount"); - /// /// Apply Theme /// public static string Buttons_ApplyTheme => Resources.GetTranslatedString(@"Buttons_ApplyTheme"); - /// /// Browse /// public static string Buttons_Browse => Resources.GetTranslatedString(@"Buttons_Browse"); - /// /// Cancel /// public static string Buttons_Cancel => Resources.GetTranslatedString(@"Buttons_Cancel"); - /// /// Close /// public static string Buttons_Close => Resources.GetTranslatedString(@"Buttons_Close"); - /// /// Create /// public static string Buttons_Create => Resources.GetTranslatedString(@"Buttons_Create"); - /// /// Create Account /// public static string Buttons_CreateAccount => Resources.GetTranslatedString(@"Buttons_CreateAccount"); - /// /// Copy /// public static string Buttons_Copy => Resources.GetTranslatedString(@"Buttons_Copy"); - /// /// Delete /// public static string Buttons_Delete => Resources.GetTranslatedString(@"Buttons_Delete"); - /// /// Edit /// public static string Buttons_Edit => Resources.GetTranslatedString(@"Buttons_Edit"); - /// /// Discard /// public static string Buttons_Discard => Resources.GetTranslatedString(@"Buttons_Discard"); - /// /// Enable /// public static string Buttons_EnableImageRendering => Resources.GetTranslatedString(@"Buttons_EnableImageRendering"); - /// /// No /// public static string Buttons_No => Resources.GetTranslatedString(@"Buttons_No"); - /// /// Open /// public static string Buttons_Open => Resources.GetTranslatedString(@"Buttons_Open"); - /// /// Purchase /// public static string Buttons_Purchase => Resources.GetTranslatedString(@"Buttons_Purchase"); - /// /// Rate Wino /// public static string Buttons_RateWino => Resources.GetTranslatedString(@"Buttons_RateWino"); - /// /// Save /// public static string Buttons_Save => Resources.GetTranslatedString(@"Buttons_Save"); - /// /// Save Configuration /// public static string Buttons_SaveConfiguration => Resources.GetTranslatedString(@"Buttons_SaveConfiguration"); - /// /// Share /// public static string Buttons_Share => Resources.GetTranslatedString(@"Buttons_Share"); - /// /// Sign In /// public static string Buttons_SignIn => Resources.GetTranslatedString(@"Buttons_SignIn"); - /// /// Try Again /// public static string Buttons_TryAgain => Resources.GetTranslatedString(@"Buttons_TryAgain"); - /// /// Yes /// public static string Buttons_Yes => Resources.GetTranslatedString(@"Buttons_Yes"); - /// /// Center /// public static string Center => Resources.GetTranslatedString(@"Center"); - /// /// Coming soon... /// public static string ComingSoon => Resources.GetTranslatedString(@"ComingSoon"); - /// /// From: /// public static string ComposerFrom => Resources.GetTranslatedString(@"ComposerFrom"); - /// /// Subject: /// public static string ComposerSubject => Resources.GetTranslatedString(@"ComposerSubject"); - /// /// To: /// public static string ComposerTo => Resources.GetTranslatedString(@"ComposerTo"); - /// /// {0} copied to clipboard. /// public static string ClipboardTextCopied_Message => Resources.GetTranslatedString(@"ClipboardTextCopied_Message"); - /// /// Copied /// public static string ClipboardTextCopied_Title => Resources.GetTranslatedString(@"ClipboardTextCopied_Title"); - /// /// Failed to copy {0} to clipboard. /// public static string ClipboardTextCopyFailed_Message => Resources.GetTranslatedString(@"ClipboardTextCopyFailed_Message"); - /// /// click enter to input addresses /// public static string ComposerToPlaceholder => Resources.GetTranslatedString(@"ComposerToPlaceholder"); - /// /// Drop your files here /// public static string ComposerAttachmentsDropZone_Message => Resources.GetTranslatedString(@"ComposerAttachmentsDropZone_Message"); - /// /// Drop your images here /// public static string ComposerImagesDropZone_Message => Resources.GetTranslatedString(@"ComposerImagesDropZone_Message"); - /// /// Attach /// public static string ComposerAttachmentsDragDropAttach_Message => Resources.GetTranslatedString(@"ComposerAttachmentsDragDropAttach_Message"); - /// /// Set custom accent color if you wish. Not selecting a color will use your Windows accent color. /// public static string CustomThemeBuilder_AccentColorDescription => Resources.GetTranslatedString(@"CustomThemeBuilder_AccentColorDescription"); - /// /// Accent color /// public static string CustomThemeBuilder_AccentColorTitle => Resources.GetTranslatedString(@"CustomThemeBuilder_AccentColorTitle"); - /// /// Pick /// public static string CustomThemeBuilder_PickColor => Resources.GetTranslatedString(@"CustomThemeBuilder_PickColor"); - /// /// Unique name for your custom theme. /// public static string CustomThemeBuilder_ThemeNameDescription => Resources.GetTranslatedString(@"CustomThemeBuilder_ThemeNameDescription"); - /// /// Theme name /// public static string CustomThemeBuilder_ThemeNameTitle => Resources.GetTranslatedString(@"CustomThemeBuilder_ThemeNameTitle"); - /// /// Custom Theme Builder /// public static string CustomThemeBuilder_Title => Resources.GetTranslatedString(@"CustomThemeBuilder_Title"); - /// /// Set a custom wallpaper for Wino /// public static string CustomThemeBuilder_WallpaperDescription => Resources.GetTranslatedString(@"CustomThemeBuilder_WallpaperDescription"); - /// /// Set custom wallpaper /// public static string CustomThemeBuilder_WallpaperTitle => Resources.GetTranslatedString(@"CustomThemeBuilder_WallpaperTitle"); - /// /// You have reached the account creation limit. Would you like to purchase 'Unlimited Account' add-on to continue? /// public static string DialogMessage_AccountLimitMessage => Resources.GetTranslatedString(@"DialogMessage_AccountLimitMessage"); - /// /// Account Limit Reached /// public static string DialogMessage_AccountLimitTitle => Resources.GetTranslatedString(@"DialogMessage_AccountLimitTitle"); - /// /// Do you want to permanently delete all the mails in this folder? /// public static string DialogMessage_CleanupFolderMessage => Resources.GetTranslatedString(@"DialogMessage_CleanupFolderMessage"); - /// /// Cleanup Folder /// public static string DialogMessage_CleanupFolderTitle => Resources.GetTranslatedString(@"DialogMessage_CleanupFolderTitle"); - /// /// Message has no recipient. /// public static string DialogMessage_ComposerMissingRecipientMessage => Resources.GetTranslatedString(@"DialogMessage_ComposerMissingRecipientMessage"); - /// /// Validation Failed /// public static string DialogMessage_ComposerValidationFailedTitle => Resources.GetTranslatedString(@"DialogMessage_ComposerValidationFailedTitle"); - /// /// Give this new link a name. Accounts will be merged under this name. /// public static string DialogMessage_CreateLinkedAccountMessage => Resources.GetTranslatedString(@"DialogMessage_CreateLinkedAccountMessage"); - /// /// Account Link Name /// public static string DialogMessage_CreateLinkedAccountTitle => Resources.GetTranslatedString(@"DialogMessage_CreateLinkedAccountTitle"); - /// /// Delete {0}? /// public static string DialogMessage_DeleteAccountConfirmationMessage => Resources.GetTranslatedString(@"DialogMessage_DeleteAccountConfirmationMessage"); - /// /// All data associated with this account will be deleted from disk permanently. /// public static string DialogMessage_DeleteAccountConfirmationTitle => Resources.GetTranslatedString(@"DialogMessage_DeleteAccountConfirmationTitle"); - /// /// This draft will be discarded. Do you want to continue? /// public static string DialogMessage_DiscardDraftConfirmationMessage => Resources.GetTranslatedString(@"DialogMessage_DiscardDraftConfirmationMessage"); - /// /// Discard Draft /// public static string DialogMessage_DiscardDraftConfirmationTitle => Resources.GetTranslatedString(@"DialogMessage_DiscardDraftConfirmationTitle"); - /// /// Permanent Delete /// public static string DialogMessage_HardDeleteConfirmationMessage => Resources.GetTranslatedString(@"DialogMessage_HardDeleteConfirmationMessage"); - /// /// Message(s) will be permanently deleted. Do you want to continue? /// public static string DialogMessage_HardDeleteConfirmationTitle => Resources.GetTranslatedString(@"DialogMessage_HardDeleteConfirmationTitle"); - /// /// You don't have any accounts to create message from. /// public static string DialogMessage_NoAccountsForCreateMailMessage => Resources.GetTranslatedString(@"DialogMessage_NoAccountsForCreateMailMessage"); - /// /// Account Missing /// public static string DialogMessage_NoAccountsForCreateMailTitle => Resources.GetTranslatedString(@"DialogMessage_NoAccountsForCreateMailTitle"); - /// /// Enter new name for linked account /// public static string DialogMessage_RenameLinkedAccountsMessage => Resources.GetTranslatedString(@"DialogMessage_RenameLinkedAccountsMessage"); - /// /// Rename Linked Account /// public static string DialogMessage_RenameLinkedAccountsTitle => Resources.GetTranslatedString(@"DialogMessage_RenameLinkedAccountsTitle"); - /// /// This operation will not delete your accounts but only break the link for shared folder connections. Do you want to continue? /// public static string DialogMessage_UnlinkAccountsConfirmationMessage => Resources.GetTranslatedString(@"DialogMessage_UnlinkAccountsConfirmationMessage"); - /// /// Unlink Accounts /// public static string DialogMessage_UnlinkAccountsConfirmationTitle => Resources.GetTranslatedString(@"DialogMessage_UnlinkAccountsConfirmationTitle"); - /// /// Missin Subject /// public static string DialogMessage_EmptySubjectConfirmation => Resources.GetTranslatedString(@"DialogMessage_EmptySubjectConfirmation"); - /// /// Message has no subject. Do you want to continue? /// public static string DialogMessage_EmptySubjectConfirmationMessage => Resources.GetTranslatedString(@"DialogMessage_EmptySubjectConfirmationMessage"); - + /// + /// Rename Folder + /// + public static string DialogMessage_RenameFolderTitle => Resources.GetTranslatedString(@"DialogMessage_RenameFolderTitle"); + + /// + /// Enter new name for this folder + /// + public static string DialogMessage_RenameFolderMessage => Resources.GetTranslatedString(@"DialogMessage_RenameFolderMessage"); + /// /// Unsubscribe /// public static string DialogMessage_UnsubscribeConfirmationTitle => Resources.GetTranslatedString(@"DialogMessage_UnsubscribeConfirmationTitle"); - /// /// Do you want to stop getting messages from {0}? /// public static string DialogMessage_UnsubscribeConfirmationOneClickMessage => Resources.GetTranslatedString(@"DialogMessage_UnsubscribeConfirmationOneClickMessage"); - /// /// To stop getting messages from {0}, go to their website to unsubscribe. /// public static string DialogMessage_UnsubscribeConfirmationGoToWebsiteMessage => Resources.GetTranslatedString(@"DialogMessage_UnsubscribeConfirmationGoToWebsiteMessage"); - /// /// Go to website /// public static string DialogMessage_UnsubscribeConfirmationGoToWebsiteConfirmButton => Resources.GetTranslatedString(@"DialogMessage_UnsubscribeConfirmationGoToWebsiteConfirmButton"); - /// /// Do you want to stop getting messages from {0}? Wino will unsubscribe for you by sending an email from your email account to {1}. /// public static string DialogMessage_UnsubscribeConfirmationMailtoMessage => Resources.GetTranslatedString(@"DialogMessage_UnsubscribeConfirmationMailtoMessage"); - /// /// Don't ask again /// public static string Dialog_DontAskAgain => Resources.GetTranslatedString(@"Dialog_DontAskAgain"); - /// /// Wino doesn't have it's own Discord server, but special 'wino-mail' channel is hosted at 'Developer Sanctuary' server. To get the updates about Wino please join Developer Sanctuary server and follow 'wino-mail' channel under 'Community Projects' You will be directed to server URL since Discord doesn't support channel invites. /// public static string DiscordChannelDisclaimerMessage => Resources.GetTranslatedString(@"DiscordChannelDisclaimerMessage"); - /// /// Important Discord Information /// public static string DiscordChannelDisclaimerTitle => Resources.GetTranslatedString(@"DiscordChannelDisclaimerTitle"); - /// /// Draft /// public static string Draft => Resources.GetTranslatedString(@"Draft"); - /// /// Draw /// public static string EditorToolbarOption_Draw => Resources.GetTranslatedString(@"EditorToolbarOption_Draw"); - /// /// Format /// public static string EditorToolbarOption_Format => Resources.GetTranslatedString(@"EditorToolbarOption_Format"); - /// /// Insert /// public static string EditorToolbarOption_Insert => Resources.GetTranslatedString(@"EditorToolbarOption_Insert"); - /// /// None /// public static string EditorToolbarOption_None => Resources.GetTranslatedString(@"EditorToolbarOption_None"); - /// /// Options /// public static string EditorToolbarOption_Options => Resources.GetTranslatedString(@"EditorToolbarOption_Options"); - /// /// Use web view editor /// public static string EditorTooltip_WebViewEditor => Resources.GetTranslatedString(@"EditorTooltip_WebViewEditor"); - /// /// Dark mode /// public static string ElementTheme_Dark => Resources.GetTranslatedString(@"ElementTheme_Dark"); - /// /// Use system setting /// public static string ElementTheme_Default => Resources.GetTranslatedString(@"ElementTheme_Default"); - /// /// Light mode /// public static string ElementTheme_Light => Resources.GetTranslatedString(@"ElementTheme_Light"); - /// /// Emoji /// public static string Emoji => Resources.GetTranslatedString(@"Emoji"); - /// /// Couldn't find mailbox settings. /// public static string Exception_ImapAutoDiscoveryFailed => Resources.GetTranslatedString(@"Exception_ImapAutoDiscoveryFailed"); - /// /// IMAP Client Pool failed. /// public static string Exception_ImapClientPoolFailed => Resources.GetTranslatedString(@"Exception_ImapClientPoolFailed"); - /// /// Authentication canceled /// public static string Exception_AuthenticationCanceled => Resources.GetTranslatedString(@"Exception_AuthenticationCanceled"); - /// /// This theme already exists. /// public static string Exception_CustomThemeExists => Resources.GetTranslatedString(@"Exception_CustomThemeExists"); - /// /// You must provide a name. /// public static string Exception_CustomThemeMissingName => Resources.GetTranslatedString(@"Exception_CustomThemeMissingName"); - /// /// You must provide a custom background image. /// public static string Exception_CustomThemeMissingWallpaper => Resources.GetTranslatedString(@"Exception_CustomThemeMissingWallpaper"); - /// /// Failed to synchronize folders /// public static string Exception_FailedToSynchronizeFolders => Resources.GetTranslatedString(@"Exception_FailedToSynchronizeFolders"); - /// /// Callback uri is null on activation. /// public static string Exception_GoogleAuthCallbackNull => Resources.GetTranslatedString(@"Exception_GoogleAuthCallbackNull"); - /// /// Corrupted authorization response. /// public static string Exception_GoogleAuthCorruptedCode => Resources.GetTranslatedString(@"Exception_GoogleAuthCorruptedCode"); - /// /// OAuth authorization error: {0} /// public static string Exception_GoogleAuthError => Resources.GetTranslatedString(@"Exception_GoogleAuthError"); - /// /// Received request with invalid state ({0}) /// public static string Exception_GoogleAuthInvalidResponse => Resources.GetTranslatedString(@"Exception_GoogleAuthInvalidResponse"); - /// /// Authorization code exchange failed. /// public static string Exception_GoogleAuthorizationCodeExchangeFailed => Resources.GetTranslatedString(@"Exception_GoogleAuthorizationCodeExchangeFailed"); - /// /// System folder configuration is not valid. Check configuration and try again. /// public static string Exception_InvalidSystemFolderConfiguration => Resources.GetTranslatedString(@"Exception_InvalidSystemFolderConfiguration"); - /// /// Assigned account is null /// public static string Exception_NullAssignedAccount => Resources.GetTranslatedString(@"Exception_NullAssignedAccount"); - /// /// Assigned folder is null /// public static string Exception_NullAssignedFolder => Resources.GetTranslatedString(@"Exception_NullAssignedFolder"); - /// /// Response handling failed with error HTTP code {0} /// public static string Exception_SynchronizerFailureHTTP => Resources.GetTranslatedString(@"Exception_SynchronizerFailureHTTP"); - /// /// Token generation failed /// public static string Exception_TokenGenerationFailed => Resources.GetTranslatedString(@"Exception_TokenGenerationFailed"); - /// /// Failed to get token information. /// public static string Exception_TokenInfoRetrivalFailed => Resources.GetTranslatedString(@"Exception_TokenInfoRetrivalFailed"); - /// /// Unknown error occurred during authentication /// public static string Exception_UnknowErrorDuringAuthentication => Resources.GetTranslatedString(@"Exception_UnknowErrorDuringAuthentication"); - /// /// Action {0} is not implemented in request processor /// public static string Exception_UnsupportedAction => Resources.GetTranslatedString(@"Exception_UnsupportedAction"); - /// /// This provider is not supported. /// public static string Exception_UnsupportedProvider => Resources.GetTranslatedString(@"Exception_UnsupportedProvider"); - /// /// This operation is not supported for {0} /// public static string Exception_UnsupportedSynchronizerOperation => Resources.GetTranslatedString(@"Exception_UnsupportedSynchronizerOperation"); - /// /// User canceled system folder config dialog. /// public static string Exception_UserCancelSystemFolderSetupDialog => Resources.GetTranslatedString(@"Exception_UserCancelSystemFolderSetupDialog"); - /// /// Couldn't setup account folders. /// public static string Exception_InboxNotAvailable => Resources.GetTranslatedString(@"Exception_InboxNotAvailable"); - /// /// Files /// public static string Files => Resources.GetTranslatedString(@"Files"); - /// /// All /// public static string FilteringOption_All => Resources.GetTranslatedString(@"FilteringOption_All"); - /// /// Flagged /// public static string FilteringOption_Flagged => Resources.GetTranslatedString(@"FilteringOption_Flagged"); - /// /// Unread /// public static string FilteringOption_Unread => Resources.GetTranslatedString(@"FilteringOption_Unread"); - /// /// Has files /// public static string FilteringOption_Files => Resources.GetTranslatedString(@"FilteringOption_Files"); - /// /// Focused /// public static string Focused => Resources.GetTranslatedString(@"Focused"); - /// /// Create sub folder /// public static string FolderOperation_CreateSubFolder => Resources.GetTranslatedString(@"FolderOperation_CreateSubFolder"); - /// /// Delete /// public static string FolderOperation_Delete => Resources.GetTranslatedString(@"FolderOperation_Delete"); - /// /// Don't sync this folder /// public static string FolderOperation_DontSync => Resources.GetTranslatedString(@"FolderOperation_DontSync"); - /// /// Empty this folder /// public static string FolderOperation_Empty => Resources.GetTranslatedString(@"FolderOperation_Empty"); - /// /// Mark all as read /// public static string FolderOperation_MarkAllAsRead => Resources.GetTranslatedString(@"FolderOperation_MarkAllAsRead"); - /// /// Move /// public static string FolderOperation_Move => Resources.GetTranslatedString(@"FolderOperation_Move"); - /// /// Move to {0} /// public static string DragMoveToFolderCaption => Resources.GetTranslatedString(@"DragMoveToFolderCaption"); - /// /// None /// public static string FolderOperation_None => Resources.GetTranslatedString(@"FolderOperation_None"); - /// /// Pin /// public static string FolderOperation_Pin => Resources.GetTranslatedString(@"FolderOperation_Pin"); - /// /// Rename /// public static string FolderOperation_Rename => Resources.GetTranslatedString(@"FolderOperation_Rename"); - /// /// Unpin /// public static string FolderOperation_Unpin => Resources.GetTranslatedString(@"FolderOperation_Unpin"); - /// /// Archive /// public static string HoverActionOption_Archive => Resources.GetTranslatedString(@"HoverActionOption_Archive"); - /// /// Delete /// public static string HoverActionOption_Delete => Resources.GetTranslatedString(@"HoverActionOption_Delete"); - /// /// Move to Junk /// public static string HoverActionOption_MoveJunk => Resources.GetTranslatedString(@"HoverActionOption_MoveJunk"); - /// /// Flag / Unflag /// public static string HoverActionOption_ToggleFlag => Resources.GetTranslatedString(@"HoverActionOption_ToggleFlag"); - /// /// Read / Unread /// public static string HoverActionOption_ToggleRead => Resources.GetTranslatedString(@"HoverActionOption_ToggleRead"); - /// /// Inbox /// public static string MergedAccountCommonFolderInbox => Resources.GetTranslatedString(@"MergedAccountCommonFolderInbox"); - /// /// Sent /// public static string MergedAccountCommonFolderSent => Resources.GetTranslatedString(@"MergedAccountCommonFolderSent"); - /// /// Draft /// public static string MergedAccountCommonFolderDraft => Resources.GetTranslatedString(@"MergedAccountCommonFolderDraft"); - /// /// Junk /// public static string MergedAccountCommonFolderJunk => Resources.GetTranslatedString(@"MergedAccountCommonFolderJunk"); - /// /// Deleted /// public static string MergedAccountCommonFolderTrash => Resources.GetTranslatedString(@"MergedAccountCommonFolderTrash"); - /// /// Archive /// public static string MergedAccountCommonFolderArchive => Resources.GetTranslatedString(@"MergedAccountCommonFolderArchive"); - /// /// Account type /// public static string IMAPSetupDialog_AccountType => Resources.GetTranslatedString(@"IMAPSetupDialog_AccountType"); - /// /// Display Name /// public static string IMAPSetupDialog_DisplayName => Resources.GetTranslatedString(@"IMAPSetupDialog_DisplayName"); - /// /// eg. John Doe /// public static string IMAPSetupDialog_DisplayNamePlaceholder => Resources.GetTranslatedString(@"IMAPSetupDialog_DisplayNamePlaceholder"); - /// /// Incoming mail server /// public static string IMAPSetupDialog_IncomingMailServer => Resources.GetTranslatedString(@"IMAPSetupDialog_IncomingMailServer"); - /// /// Port /// public static string IMAPSetupDialog_IncomingMailServerPort => Resources.GetTranslatedString(@"IMAPSetupDialog_IncomingMailServerPort"); - /// /// Email address /// public static string IMAPSetupDialog_MailAddress => Resources.GetTranslatedString(@"IMAPSetupDialog_MailAddress"); - /// /// someone@example.com /// public static string IMAPSetupDialog_MailAddressPlaceholder => Resources.GetTranslatedString(@"IMAPSetupDialog_MailAddressPlaceholder"); - /// /// Outgoing (SMTP) mail server /// public static string IMAPSetupDialog_OutgoingMailServer => Resources.GetTranslatedString(@"IMAPSetupDialog_OutgoingMailServer"); - /// /// Outgoing server password /// public static string IMAPSetupDialog_OutgoingMailServerPassword => Resources.GetTranslatedString(@"IMAPSetupDialog_OutgoingMailServerPassword"); - /// /// Port /// public static string IMAPSetupDialog_OutgoingMailServerPort => Resources.GetTranslatedString(@"IMAPSetupDialog_OutgoingMailServerPort"); - /// /// Outgoing server requires authentication /// public static string IMAPSetupDialog_OutgoingMailServerRequireAuthentication => Resources.GetTranslatedString(@"IMAPSetupDialog_OutgoingMailServerRequireAuthentication"); - /// /// Outgoing server user name /// public static string IMAPSetupDialog_OutgoingMailServerUsername => Resources.GetTranslatedString(@"IMAPSetupDialog_OutgoingMailServerUsername"); - /// /// Password /// public static string IMAPSetupDialog_Password => Resources.GetTranslatedString(@"IMAPSetupDialog_Password"); - /// /// Require SSL for incoming email /// public static string IMAPSetupDialog_RequireSSLForIncomingMail => Resources.GetTranslatedString(@"IMAPSetupDialog_RequireSSLForIncomingMail"); - /// /// Require SSL for outgoing email /// public static string IMAPSetupDialog_RequireSSLForOutgoingMail => Resources.GetTranslatedString(@"IMAPSetupDialog_RequireSSLForOutgoingMail"); - /// /// Advanced IMAP Configuration /// public static string IMAPSetupDialog_Title => Resources.GetTranslatedString(@"IMAPSetupDialog_Title"); - /// /// Use the same username and password for sending email /// public static string IMAPSetupDialog_UseSameConfig => Resources.GetTranslatedString(@"IMAPSetupDialog_UseSameConfig"); - /// /// Username /// public static string IMAPSetupDialog_Username => Resources.GetTranslatedString(@"IMAPSetupDialog_Username"); - /// /// johndoe, johndoe@fabrikam.com, domain/johndoe /// public static string IMAPSetupDialog_UsernamePlaceholder => Resources.GetTranslatedString(@"IMAPSetupDialog_UsernamePlaceholder"); - /// /// Connection Failed /// public static string IMAPSetupDialog_ConnectionFailedTitle => Resources.GetTranslatedString(@"IMAPSetupDialog_ConnectionFailedTitle"); - /// /// IMAP connection failed. /// public static string IMAPSetupDialog_ConnectionFailedMessage => Resources.GetTranslatedString(@"IMAPSetupDialog_ConnectionFailedMessage"); - /// /// Image rendering is disabled for this message. /// public static string ImageRenderingDisabled => Resources.GetTranslatedString(@"ImageRenderingDisabled"); - /// /// Enable /// public static string InfoBarAction_Enable => Resources.GetTranslatedString(@"InfoBarAction_Enable"); - /// /// This folder is disabled for synchronization. /// public static string InfoBarMessage_SynchronizationDisabledFolder => Resources.GetTranslatedString(@"InfoBarMessage_SynchronizationDisabledFolder"); - /// /// Disabled Folder /// public static string InfoBarTitle_SynchronizationDisabledFolder => Resources.GetTranslatedString(@"InfoBarTitle_SynchronizationDisabledFolder"); - /// /// Error /// public static string GeneralTitle_Error => Resources.GetTranslatedString(@"GeneralTitle_Error"); - /// /// Warning /// public static string GeneralTitle_Warning => Resources.GetTranslatedString(@"GeneralTitle_Warning"); - /// /// Information /// public static string GeneralTitle_Info => Resources.GetTranslatedString(@"GeneralTitle_Info"); - /// /// {0} is created /// public static string Info_AccountCreatedMessage => Resources.GetTranslatedString(@"Info_AccountCreatedMessage"); - /// /// Account Creation /// public static string Info_AccountCreatedTitle => Resources.GetTranslatedString(@"Info_AccountCreatedTitle"); - /// /// Account Creation Failed /// public static string Info_AccountCreationFailedTitle => Resources.GetTranslatedString(@"Info_AccountCreationFailedTitle"); - /// /// {0} is successfuly deleted. /// public static string Info_AccountDeletedMessage => Resources.GetTranslatedString(@"Info_AccountDeletedMessage"); - /// /// Account Deleted /// public static string Info_AccountDeletedTitle => Resources.GetTranslatedString(@"Info_AccountDeletedTitle"); - /// /// Failed /// public static string Info_AccountIssueFixFailedTitle => Resources.GetTranslatedString(@"Info_AccountIssueFixFailedTitle"); - /// /// Fixed all account issues. /// public static string Info_AccountIssueFixSuccessMessage => Resources.GetTranslatedString(@"Info_AccountIssueFixSuccessMessage"); - /// /// Success /// public static string Info_AccountIssueFixSuccessTitle => Resources.GetTranslatedString(@"Info_AccountIssueFixSuccessTitle"); - /// /// Can't open this attachment. /// public static string Info_AttachmentOpenFailedMessage => Resources.GetTranslatedString(@"Info_AttachmentOpenFailedMessage"); - /// /// Failed /// public static string Info_AttachmentOpenFailedTitle => Resources.GetTranslatedString(@"Info_AttachmentOpenFailedTitle"); - /// /// Can't save this attachment. /// public static string Info_AttachmentSaveFailedMessage => Resources.GetTranslatedString(@"Info_AttachmentSaveFailedMessage"); - /// /// Failed /// public static string Info_AttachmentSaveFailedTitle => Resources.GetTranslatedString(@"Info_AttachmentSaveFailedTitle"); - /// /// Attachment is saved. /// public static string Info_AttachmentSaveSuccessMessage => Resources.GetTranslatedString(@"Info_AttachmentSaveSuccessMessage"); - /// /// Attachment Saved /// public static string Info_AttachmentSaveSuccessTitle => Resources.GetTranslatedString(@"Info_AttachmentSaveSuccessTitle"); - /// /// Background execution for the app is denied. This may affect background synchronization and live notifications. /// public static string Info_BackgroundExecutionDeniedMessage => Resources.GetTranslatedString(@"Info_BackgroundExecutionDeniedMessage"); - /// /// Denied Background Execution /// public static string Info_BackgroundExecutionDeniedTitle => Resources.GetTranslatedString(@"Info_BackgroundExecutionDeniedTitle"); - /// /// Unknown exception occurred when registering background synchronizer. /// public static string Info_BackgroundExecutionUnknownErrorMessage => Resources.GetTranslatedString(@"Info_BackgroundExecutionUnknownErrorMessage"); - /// /// Background Execution Failure /// public static string Info_BackgroundExecutionUnknownErrorTitle => Resources.GetTranslatedString(@"Info_BackgroundExecutionUnknownErrorTitle"); - /// /// Couldn't locate the MIME file. Synchronizing may help. /// public static string Info_ComposerMissingMIMEMessage => Resources.GetTranslatedString(@"Info_ComposerMissingMIMEMessage"); - /// /// Failed /// public static string Info_ComposerMissingMIMETitle => Resources.GetTranslatedString(@"Info_ComposerMissingMIMETitle"); - /// /// This contact is already in the recipient list. /// public static string Info_ContactExistsMessage => Resources.GetTranslatedString(@"Info_ContactExistsMessage"); - /// /// Contact Exists /// public static string Info_ContactExistsTitle => Resources.GetTranslatedString(@"Info_ContactExistsTitle"); - /// /// Draft folder is missing for this account. Please check your account settings. /// public static string Info_DraftFolderMissingMessage => Resources.GetTranslatedString(@"Info_DraftFolderMissingMessage"); - /// /// Missing Draft Folder /// public static string Info_DraftFolderMissingTitle => Resources.GetTranslatedString(@"Info_DraftFolderMissingTitle"); - /// /// Failed to launch file /// public static string Info_FileLaunchFailedTitle => Resources.GetTranslatedString(@"Info_FileLaunchFailedTitle"); - /// /// '{0}' is not a valid e-mail address. /// public static string Info_InvalidAddressMessage => Resources.GetTranslatedString(@"Info_InvalidAddressMessage"); - /// /// Invalid Address /// public static string Info_InvalidAddressTitle => Resources.GetTranslatedString(@"Info_InvalidAddressTitle"); - /// /// You can't move selected mails to this folder. /// public static string Info_InvalidMoveTargetMessage => Resources.GetTranslatedString(@"Info_InvalidMoveTargetMessage"); - /// /// Invalid Move Target /// public static string Info_InvalidMoveTargetTitle => Resources.GetTranslatedString(@"Info_InvalidMoveTargetTitle"); - /// /// There are no logs to share. /// public static string Info_LogsNotFoundMessage => Resources.GetTranslatedString(@"Info_LogsNotFoundMessage"); - /// /// Logs Not Found /// public static string Info_LogsNotFoundTitle => Resources.GetTranslatedString(@"Info_LogsNotFoundTitle"); - /// /// {0} is saved to selected folder. /// public static string Info_LogsSavedMessage => Resources.GetTranslatedString(@"Info_LogsSavedMessage"); - /// /// Saved /// public static string Info_LogsSavedTitle => Resources.GetTranslatedString(@"Info_LogsSavedTitle"); - /// /// This mail is corrupted or can't be opened. {0} /// public static string Info_MailRenderingFailedMessage => Resources.GetTranslatedString(@"Info_MailRenderingFailedMessage"); - /// /// Render Failed /// public static string Info_MailRenderingFailedTitle => Resources.GetTranslatedString(@"Info_MailRenderingFailedTitle"); - /// /// This message is corrupted. /// public static string Info_MessageCorruptedMessage => Resources.GetTranslatedString(@"Info_MessageCorruptedMessage"); - /// /// Error /// public static string Info_MessageCorruptedTitle => Resources.GetTranslatedString(@"Info_MessageCorruptedTitle"); - /// /// {0} doesn't exist for this account. /// public static string Info_MissingFolderMessage => Resources.GetTranslatedString(@"Info_MissingFolderMessage"); - /// /// Missing Folder /// public static string Info_MissingFolderTitle => Resources.GetTranslatedString(@"Info_MissingFolderTitle"); - /// /// Success /// public static string Info_PDFSaveSuccessTitle => Resources.GetTranslatedString(@"Info_PDFSaveSuccessTitle"); - /// /// Failed to save PDF file /// public static string Info_PDFSaveFailedTitle => Resources.GetTranslatedString(@"Info_PDFSaveFailedTitle"); - /// /// PDF file is saved to {0} /// public static string Info_PDFSaveSuccessMessage => Resources.GetTranslatedString(@"Info_PDFSaveSuccessMessage"); - /// /// Looks like this product has already been purchased before. /// public static string Info_PurchaseExistsMessage => Resources.GetTranslatedString(@"Info_PurchaseExistsMessage"); - /// /// Existing Product /// public static string Info_PurchaseExistsTitle => Resources.GetTranslatedString(@"Info_PurchaseExistsTitle"); - /// /// Thank You /// public static string Info_PurchaseThankYouMessage => Resources.GetTranslatedString(@"Info_PurchaseThankYouMessage"); - /// /// Purchase successful /// public static string Info_PurchaseThankYouTitle => Resources.GetTranslatedString(@"Info_PurchaseThankYouTitle"); - /// /// Failed to Create Requests /// public static string Info_RequestCreationFailedTitle => Resources.GetTranslatedString(@"Info_RequestCreationFailedTitle"); - /// /// There was a network issue with your review. /// public static string Info_ReviewNetworkErrorMessage => Resources.GetTranslatedString(@"Info_ReviewNetworkErrorMessage"); - /// /// Network Issue /// public static string Info_ReviewNetworkErrorTitle => Resources.GetTranslatedString(@"Info_ReviewNetworkErrorTitle"); - /// /// All feedbacks are appreciated. Thank you for the review! /// public static string Info_ReviewNewMessage => Resources.GetTranslatedString(@"Info_ReviewNewMessage"); - /// /// Thank you /// public static string Info_ReviewSuccessTitle => Resources.GetTranslatedString(@"Info_ReviewSuccessTitle"); - /// /// There was an unknown issue with your review. ({0}) /// public static string Info_ReviewUnknownErrorMessage => Resources.GetTranslatedString(@"Info_ReviewUnknownErrorMessage"); - /// /// Unknown Error /// public static string Info_ReviewUnknownErrorTitle => Resources.GetTranslatedString(@"Info_ReviewUnknownErrorTitle"); - /// /// Thank you for the updated review. /// public static string Info_ReviewUpdatedMessage => Resources.GetTranslatedString(@"Info_ReviewUpdatedMessage"); - /// /// Disabled signature for this account /// public static string Info_SignatureDisabledMessage => Resources.GetTranslatedString(@"Info_SignatureDisabledMessage"); - /// /// Success /// public static string Info_SignatureDisabledTitle => Resources.GetTranslatedString(@"Info_SignatureDisabledTitle"); - /// /// New signature is saved /// public static string Info_SignatureSavedMessage => Resources.GetTranslatedString(@"Info_SignatureSavedMessage"); - /// /// Success /// public static string Info_SignatureSavedTitle => Resources.GetTranslatedString(@"Info_SignatureSavedTitle"); - /// /// Canceled /// public static string Info_SyncCanceledMessage => Resources.GetTranslatedString(@"Info_SyncCanceledMessage"); - /// /// Synchronization /// public static string Info_SyncCanceledTitle => Resources.GetTranslatedString(@"Info_SyncCanceledTitle"); - /// /// Synchronization Failed /// public static string Info_SyncFailedTitle => Resources.GetTranslatedString(@"Info_SyncFailedTitle"); - /// /// This functionality is not implemented yet. /// public static string Info_UnsupportedFunctionalityDescription => Resources.GetTranslatedString(@"Info_UnsupportedFunctionalityDescription"); - /// /// Unsupported /// public static string Info_UnsupportedFunctionalityTitle => Resources.GetTranslatedString(@"Info_UnsupportedFunctionalityTitle"); - /// /// Invalid Unsubscribe Uri /// public static string Info_UnsubscribeLinkInvalidTitle => Resources.GetTranslatedString(@"Info_UnsubscribeLinkInvalidTitle"); - /// /// This unsubscribe link is invalid. Failed to unsubscribe from the list. /// public static string Info_UnsubscribeLinkInvalidMessage => Resources.GetTranslatedString(@"Info_UnsubscribeLinkInvalidMessage"); - /// /// Successfully unsubscribed from {0}. /// public static string Info_UnsubscribeSuccessMessage => Resources.GetTranslatedString(@"Info_UnsubscribeSuccessMessage"); - /// /// Failed to unsubscribe /// public static string Info_UnsubscribeErrorMessage => Resources.GetTranslatedString(@"Info_UnsubscribeErrorMessage"); - /// /// Authentication method /// public static string ImapAdvancedSetupDialog_AuthenticationMethod => Resources.GetTranslatedString(@"ImapAdvancedSetupDialog_AuthenticationMethod"); - /// /// Connection security /// public static string ImapAdvancedSetupDialog_ConnectionSecurity => Resources.GetTranslatedString(@"ImapAdvancedSetupDialog_ConnectionSecurity"); - /// /// Auto /// public static string ImapAuthenticationMethod_Auto => Resources.GetTranslatedString(@"ImapAuthenticationMethod_Auto"); - /// /// CRAM-MD5 /// public static string ImapAuthenticationMethod_CramMD5 => Resources.GetTranslatedString(@"ImapAuthenticationMethod_CramMD5"); - /// /// DIGEST-MD5 /// public static string ImapAuthenticationMethod_DigestMD5 => Resources.GetTranslatedString(@"ImapAuthenticationMethod_DigestMD5"); - /// /// No authentication /// public static string ImapAuthenticationMethod_None => Resources.GetTranslatedString(@"ImapAuthenticationMethod_None"); - /// /// Normal password /// public static string ImapAuthenticationMethod_Plain => Resources.GetTranslatedString(@"ImapAuthenticationMethod_Plain"); - /// /// Encrypted password /// public static string ImapAuthenticationMethod_EncryptedPassword => Resources.GetTranslatedString(@"ImapAuthenticationMethod_EncryptedPassword"); - /// /// NTLM /// public static string ImapAuthenticationMethod_Ntlm => Resources.GetTranslatedString(@"ImapAuthenticationMethod_Ntlm"); - /// /// None /// public static string ImapConnectionSecurity_None => Resources.GetTranslatedString(@"ImapConnectionSecurity_None"); - /// /// SSL/TLS /// public static string ImapConnectionSecurity_SslTls => Resources.GetTranslatedString(@"ImapConnectionSecurity_SslTls"); - /// /// STARTTLS /// public static string ImapConnectionSecurity_StartTls => Resources.GetTranslatedString(@"ImapConnectionSecurity_StartTls"); - /// /// Auto /// public static string ImapConnectionSecurity_Auto => Resources.GetTranslatedString(@"ImapConnectionSecurity_Auto"); - /// /// Justify /// public static string Justify => Resources.GetTranslatedString(@"Justify"); - /// /// Left /// public static string Left => Resources.GetTranslatedString(@"Left"); - /// /// Link /// public static string Link => Resources.GetTranslatedString(@"Link"); - /// /// you must have at least 2 accounts to create link link will be removed on save /// public static string LinkedAccountsCreatePolicyMessage => Resources.GetTranslatedString(@"LinkedAccountsCreatePolicyMessage"); - /// /// Linked Accounts /// public static string LinkedAccountsTitle => Resources.GetTranslatedString(@"LinkedAccountsTitle"); - /// /// Always Move to Focused /// public static string MailOperation_AlwaysMoveFocused => Resources.GetTranslatedString(@"MailOperation_AlwaysMoveFocused"); - /// /// Always Move to Other /// public static string MailOperation_AlwaysMoveOther => Resources.GetTranslatedString(@"MailOperation_AlwaysMoveOther"); - /// /// Archive /// public static string MailOperation_Archive => Resources.GetTranslatedString(@"MailOperation_Archive"); - /// /// Clear flag /// public static string MailOperation_ClearFlag => Resources.GetTranslatedString(@"MailOperation_ClearFlag"); - /// /// Dark /// public static string MailOperation_DarkEditor => Resources.GetTranslatedString(@"MailOperation_DarkEditor"); - /// /// Delete /// public static string MailOperation_Delete => Resources.GetTranslatedString(@"MailOperation_Delete"); - /// /// Export to PDF /// public static string MailOperation_ExportPDF => Resources.GetTranslatedString(@"MailOperation_ExportPDF"); - /// /// Find /// public static string MailOperation_Find => Resources.GetTranslatedString(@"MailOperation_Find"); - /// /// Forward /// public static string MailOperation_Forward => Resources.GetTranslatedString(@"MailOperation_Forward"); - /// /// Ignore /// public static string MailOperation_Ignore => Resources.GetTranslatedString(@"MailOperation_Ignore"); - /// /// Light /// public static string MailOperation_LightEditor => Resources.GetTranslatedString(@"MailOperation_LightEditor"); - /// /// Mark as junk /// public static string MailOperation_MarkAsJunk => Resources.GetTranslatedString(@"MailOperation_MarkAsJunk"); - /// /// Mark as read /// public static string MailOperation_MarkAsRead => Resources.GetTranslatedString(@"MailOperation_MarkAsRead"); - /// /// Mark as unread /// public static string MailOperation_MarkAsUnread => Resources.GetTranslatedString(@"MailOperation_MarkAsUnread"); - /// /// Mark as Not Junk /// public static string MailOperation_MarkNotJunk => Resources.GetTranslatedString(@"MailOperation_MarkNotJunk"); - /// /// Move /// public static string MailOperation_Move => Resources.GetTranslatedString(@"MailOperation_Move"); - /// /// Move to Focused /// public static string MailOperation_MoveFocused => Resources.GetTranslatedString(@"MailOperation_MoveFocused"); - /// /// Move to Junk /// public static string MailOperation_MoveJunk => Resources.GetTranslatedString(@"MailOperation_MoveJunk"); - /// /// Move to Other /// public static string MailOperation_MoveOther => Resources.GetTranslatedString(@"MailOperation_MoveOther"); - /// /// Navigate /// public static string MailOperation_Navigate => Resources.GetTranslatedString(@"MailOperation_Navigate"); - /// /// Print /// public static string MailOperation_Print => Resources.GetTranslatedString(@"MailOperation_Print"); - /// /// Reply /// public static string MailOperation_Reply => Resources.GetTranslatedString(@"MailOperation_Reply"); - /// /// Reply all /// public static string MailOperation_ReplyAll => Resources.GetTranslatedString(@"MailOperation_ReplyAll"); - /// /// Save As /// public static string MailOperation_SaveAs => Resources.GetTranslatedString(@"MailOperation_SaveAs"); - /// /// Set flag /// public static string MailOperation_SetFlag => Resources.GetTranslatedString(@"MailOperation_SetFlag"); - /// /// Unarchive /// public static string MailOperation_Unarchive => Resources.GetTranslatedString(@"MailOperation_Unarchive"); - /// /// Zoom /// public static string MailOperation_Zoom => Resources.GetTranslatedString(@"MailOperation_Zoom"); - /// /// {0} item(s) selected /// public static string MailsSelected => Resources.GetTranslatedString(@"MailsSelected"); - /// /// Mark as flagged/unflagged /// public static string MarkFlagUnflag => Resources.GetTranslatedString(@"MarkFlagUnflag"); - /// /// Mark as read/unread /// public static string MarkReadUnread => Resources.GetTranslatedString(@"MarkReadUnread"); - /// /// Manage Accounts /// public static string MenuManageAccounts => Resources.GetTranslatedString(@"MenuManageAccounts"); - /// /// New Mail /// public static string MenuNewMail => Resources.GetTranslatedString(@"MenuNewMail"); - /// /// accounts /// public static string MenuMergedAccountItemAccountsSuffix => Resources.GetTranslatedString(@"MenuMergedAccountItemAccountsSuffix"); - /// /// Rate Wino /// public static string MenuRate => Resources.GetTranslatedString(@"MenuRate"); - /// /// Settings /// public static string MenuSettings => Resources.GetTranslatedString(@"MenuSettings"); - /// /// Available Accounts /// public static string MergedAccountsAvailableAccountsTitle => Resources.GetTranslatedString(@"MergedAccountsAvailableAccountsTitle"); - /// /// More /// public static string More => Resources.GetTranslatedString(@"More"); - /// /// {0} is not a valid folder for this mail. /// public static string MoveMailDialog_InvalidFolderMessage => Resources.GetTranslatedString(@"MoveMailDialog_InvalidFolderMessage"); - /// /// Pick a folder /// public static string MoveMailDialog_Title => Resources.GetTranslatedString(@"MoveMailDialog_Title"); - /// /// Account Name /// public static string NewAccountDialog_AccountName => Resources.GetTranslatedString(@"NewAccountDialog_AccountName"); - /// /// Personal /// public static string NewAccountDialog_AccountNameDefaultValue => Resources.GetTranslatedString(@"NewAccountDialog_AccountNameDefaultValue"); - /// /// eg. Personal Mail /// public static string NewAccountDialog_AccountNamePlaceholder => Resources.GetTranslatedString(@"NewAccountDialog_AccountNamePlaceholder"); - /// /// Add New Account /// public static string NewAccountDialog_Title => Resources.GetTranslatedString(@"NewAccountDialog_Title"); - /// /// No message selected /// public static string NoMailSelected => Resources.GetTranslatedString(@"NoMailSelected"); - /// /// No messages match your search criteria /// public static string NoMessageCrieteria => Resources.GetTranslatedString(@"NoMessageCrieteria"); - /// /// This folder is empty /// public static string NoMessageEmptyFolder => Resources.GetTranslatedString(@"NoMessageEmptyFolder"); - /// /// You have {0} new mails /// public static string Notifications_MultipleNotificationsMessage => Resources.GetTranslatedString(@"Notifications_MultipleNotificationsMessage"); - /// /// New Mails /// public static string Notifications_MultipleNotificationsTitle => Resources.GetTranslatedString(@"Notifications_MultipleNotificationsTitle"); - /// /// Checkout new version {0} /// public static string Notifications_WinoUpdatedMessage => Resources.GetTranslatedString(@"Notifications_WinoUpdatedMessage"); - /// /// Wino Mail has been updated. /// public static string Notifications_WinoUpdatedTitle => Resources.GetTranslatedString(@"Notifications_WinoUpdatedTitle"); - /// /// Other /// public static string Other => Resources.GetTranslatedString(@"Other"); - /// /// Default /// public static string PaneLengthOption_Default => Resources.GetTranslatedString(@"PaneLengthOption_Default"); - /// /// Extra Large /// public static string PaneLengthOption_ExtraLarge => Resources.GetTranslatedString(@"PaneLengthOption_ExtraLarge"); - /// /// Large /// public static string PaneLengthOption_Large => Resources.GetTranslatedString(@"PaneLengthOption_Large"); - /// /// Medium /// public static string PaneLengthOption_Medium => Resources.GetTranslatedString(@"PaneLengthOption_Medium"); - /// /// Micro /// public static string PaneLengthOption_Micro => Resources.GetTranslatedString(@"PaneLengthOption_Micro"); - /// /// Small /// public static string PaneLengthOption_Small => Resources.GetTranslatedString(@"PaneLengthOption_Small"); - /// /// Photos /// public static string Photos => Resources.GetTranslatedString(@"Photos"); - /// /// Preparing folders /// public static string PreparingFoldersMessage => Resources.GetTranslatedString(@"PreparingFoldersMessage"); - /// /// Google Account /// public static string ProviderDetail_Gmail_Description => Resources.GetTranslatedString(@"ProviderDetail_Gmail_Description"); - /// /// Custom IMAP/SMTP server /// public static string ProviderDetail_IMAP_Description => Resources.GetTranslatedString(@"ProviderDetail_IMAP_Description"); - /// /// IMAP Server /// public static string ProviderDetail_IMAP_Title => Resources.GetTranslatedString(@"ProviderDetail_IMAP_Title"); - /// /// Protocol logs are available for diagnostics. /// public static string ProtocolLogAvailable_Message => Resources.GetTranslatedString(@"ProtocolLogAvailable_Message"); - /// /// Results /// public static string Results => Resources.GetTranslatedString(@"Results"); - /// /// Right /// public static string Right => Resources.GetTranslatedString(@"Right"); - /// /// up to date /// public static string SynchronizationFolderReport_Success => Resources.GetTranslatedString(@"SynchronizationFolderReport_Success"); - /// /// synchronization is failed /// public static string SynchronizationFolderReport_Failed => Resources.GetTranslatedString(@"SynchronizationFolderReport_Failed"); - /// /// Search /// public static string SearchBarPlaceholder => Resources.GetTranslatedString(@"SearchBarPlaceholder"); - /// /// Searching in /// public static string SearchingIn => Resources.GetTranslatedString(@"SearchingIn"); - /// /// Results /// public static string SearchPivotName => Resources.GetTranslatedString(@"SearchPivotName"); - /// /// Go to issue tracker GitHub repository. /// public static string SettingsAboutGithub_Description => Resources.GetTranslatedString(@"SettingsAboutGithub_Description"); - /// /// GitHub /// public static string SettingsAboutGithub_Title => Resources.GetTranslatedString(@"SettingsAboutGithub_Title"); - /// /// Append messages to Sent folder /// public static string SettingsAccountManagementAppendMessage_Title => Resources.GetTranslatedString(@"SettingsAccountManagementAppendMessage_Title"); - /// /// Create a copy of the message in Sent folder after the draft is sent. Enable this if you don't see your mails after you sent them in Sent folder. /// public static string SettingsAccountManagementAppendMessage_Description => Resources.GetTranslatedString(@"SettingsAccountManagementAppendMessage_Description"); - /// /// Edit Linked Inbox /// public static string SettingsEditLinkedInbox_Title => Resources.GetTranslatedString(@"SettingsEditLinkedInbox_Title"); - /// /// Add / remove accounts, rename or break the link between accounts. /// public static string SettingsEditLinkedInbox_Description => Resources.GetTranslatedString(@"SettingsEditLinkedInbox_Description"); - /// /// Version /// public static string SettingsAboutVersion => Resources.GetTranslatedString(@"SettingsAboutVersion"); - /// /// Lightweight mail client for Windows device families. /// public static string SettingsAboutWinoDescription => Resources.GetTranslatedString(@"SettingsAboutWinoDescription"); - /// /// Learn more about Wino. /// public static string SettingsAbout_Description => Resources.GetTranslatedString(@"SettingsAbout_Description"); - /// /// About /// public static string SettingsAbout_Title => Resources.GetTranslatedString(@"SettingsAbout_Title"); - /// /// Change application's accent color /// public static string SettingsAccentColor_Description => Resources.GetTranslatedString(@"SettingsAccentColor_Description"); - /// /// Accent Color /// public static string SettingsAccentColor_Title => Resources.GetTranslatedString(@"SettingsAccentColor_Title"); - /// /// Use my Windows accent color /// public static string SettingsAccentColor_UseWindowsAccentColor => Resources.GetTranslatedString(@"SettingsAccentColor_UseWindowsAccentColor"); - /// /// Change the name of the account. /// public static string SettingsAccountName_Description => Resources.GetTranslatedString(@"SettingsAccountName_Description"); - /// /// Account Name /// public static string SettingsAccountName_Title => Resources.GetTranslatedString(@"SettingsAccountName_Title"); - /// /// Personalize Wino with different custom application themes for your like. /// public static string SettingsApplicationTheme_Description => Resources.GetTranslatedString(@"SettingsApplicationTheme_Description"); - /// /// Application Theme /// public static string SettingsApplicationTheme_Title => Resources.GetTranslatedString(@"SettingsApplicationTheme_Title"); - /// /// Select a theme from Wino's own collection for your taste or apply your own themes. /// public static string SettingsAvailableThemes_Description => Resources.GetTranslatedString(@"SettingsAvailableThemes_Description"); - /// /// Available Themes /// public static string SettingsAvailableThemes_Title => Resources.GetTranslatedString(@"SettingsAvailableThemes_Title"); - /// /// Auto select next item /// public static string SettingsAutoSelectNextItem_Title => Resources.GetTranslatedString(@"SettingsAutoSelectNextItem_Title"); - /// /// Select the next item after you delete or move a mail. /// public static string SettingsAutoSelectNextItem_Description => Resources.GetTranslatedString(@"SettingsAutoSelectNextItem_Description"); - /// /// Create your own custom theme with custom wallpaper and accent color. /// public static string SettingsCustomTheme_Description => Resources.GetTranslatedString(@"SettingsCustomTheme_Description"); - /// /// Custom Theme /// public static string SettingsCustomTheme_Title => Resources.GetTranslatedString(@"SettingsCustomTheme_Title"); - /// /// Configure System Folders /// public static string SettingsConfigureSpecialFolders_Title => Resources.GetTranslatedString(@"SettingsConfigureSpecialFolders_Title"); - /// /// Set folders with special functions. Folders such as Archive, Inbox, and Drafts are essential for Wino to function properly. /// public static string SettingsConfigureSpecialFolders_Description => Resources.GetTranslatedString(@"SettingsConfigureSpecialFolders_Description"); - /// /// Configure /// public static string SettingConfigureSpecialFolders_Button => Resources.GetTranslatedString(@"SettingConfigureSpecialFolders_Button"); - /// /// Failed to setup system folders /// public static string Error_FailedToSetupSystemFolders_Title => Resources.GetTranslatedString(@"Error_FailedToSetupSystemFolders_Title"); - /// /// Delete all e-mails and credentials associated with this account. /// public static string SettingsDeleteAccount_Description => Resources.GetTranslatedString(@"SettingsDeleteAccount_Description"); - /// /// Delete this account /// public static string SettingsDeleteAccount_Title => Resources.GetTranslatedString(@"SettingsDeleteAccount_Title"); - /// /// Should Wino ask you for comfirmation every time you try to permanently delete a mail using Shift + Del keys? /// public static string SettingsDeleteProtection_Description => Resources.GetTranslatedString(@"SettingsDeleteProtection_Description"); - /// /// Permanent Delete Protection /// public static string SettingsDeleteProtection_Title => Resources.GetTranslatedString(@"SettingsDeleteProtection_Title"); - /// /// For developers /// public static string SettingsDiagnostics_Description => Resources.GetTranslatedString(@"SettingsDiagnostics_Description"); - /// /// Diagnostics /// public static string SettingsDiagnostics_Title => Resources.GetTranslatedString(@"SettingsDiagnostics_Title"); - /// /// Get regular development updates, join roadmap discussions and provide feedback. /// public static string SettingsDiscord_Description => Resources.GetTranslatedString(@"SettingsDiscord_Description"); - /// /// Discord Channel /// public static string SettingsDiscord_Title => Resources.GetTranslatedString(@"SettingsDiscord_Title"); - /// /// Element theme selection is disabled when application theme is selected other than Default. /// public static string SettingsElementThemeSelectionDisabled => Resources.GetTranslatedString(@"SettingsElementThemeSelectionDisabled"); - /// /// Select a Windows theme for Wino /// public static string SettingsElementTheme_Description => Resources.GetTranslatedString(@"SettingsElementTheme_Description"); - /// /// Element Theme /// public static string SettingsElementTheme_Title => Resources.GetTranslatedString(@"SettingsElementTheme_Title"); - /// /// Enable hover actions /// public static string SettingsEnableHoverActions_Title => Resources.GetTranslatedString(@"SettingsEnableHoverActions_Title"); - /// /// Enable this to provide details about IMAP connectivity issuses you had during IMAP server setup. /// public static string SettingsEnableIMAPLogs_Description => Resources.GetTranslatedString(@"SettingsEnableIMAPLogs_Description"); - /// /// Enable IMAP Protocol Logs /// public static string SettingsEnableIMAPLogs_Title => Resources.GetTranslatedString(@"SettingsEnableIMAPLogs_Title"); - /// /// I might need logs for crashes to diagnose issues you have opened in GitHub. None of the logs will expose your credentials or sensetive information to public. /// public static string SettingsEnableLogs_Description => Resources.GetTranslatedString(@"SettingsEnableLogs_Description"); - /// /// Enable Logs /// public static string SettingsEnableLogs_Title => Resources.GetTranslatedString(@"SettingsEnableLogs_Title"); - /// /// Enable Signature /// public static string SettingsEnableSignature => Resources.GetTranslatedString(@"SettingsEnableSignature"); - /// /// Set whether Wino should expand this account's folders on startup. /// public static string SettingsExpandOnStartup_Description => Resources.GetTranslatedString(@"SettingsExpandOnStartup_Description"); - /// /// Expand Menu on Startup /// public static string SettingsExpandOnStartup_Title => Resources.GetTranslatedString(@"SettingsExpandOnStartup_Title"); - /// /// Manage external content settings when rendering mails. /// public static string SettingsExternalContent_Description => Resources.GetTranslatedString(@"SettingsExternalContent_Description"); - /// /// External Content /// public static string SettingsExternalContent_Title => Resources.GetTranslatedString(@"SettingsExternalContent_Title"); - /// /// Set whether Inbox should be split into two as Focused - Other. /// public static string SettingsFocusedInbox_Description => Resources.GetTranslatedString(@"SettingsFocusedInbox_Description"); - /// /// Focused Inbox /// public static string SettingsFocusedInbox_Title => Resources.GetTranslatedString(@"SettingsFocusedInbox_Title"); - /// /// Enable or disable specific folders for synchronization. /// public static string SettingsFolderSync_Description => Resources.GetTranslatedString(@"SettingsFolderSync_Description"); - /// /// Folder Synchronization /// public static string SettingsFolderSync_Title => Resources.GetTranslatedString(@"SettingsFolderSync_Title"); - /// /// Folder Configuration /// public static string SettingsFolderOptions_Title => Resources.GetTranslatedString(@"SettingsFolderOptions_Title"); - /// /// Change individual folder settings like enable/disable sync or show/hide unread badge. /// public static string SettingsFolderOptions_Description => Resources.GetTranslatedString(@"SettingsFolderOptions_Description"); - /// /// Center Action /// public static string SettingsHoverActionCenter => Resources.GetTranslatedString(@"SettingsHoverActionCenter"); - /// /// Left Action /// public static string SettingsHoverActionLeft => Resources.GetTranslatedString(@"SettingsHoverActionLeft"); - /// /// Right Action /// public static string SettingsHoverActionRight => Resources.GetTranslatedString(@"SettingsHoverActionRight"); - /// /// Select 3 actions to show up when you hover over the mails with cursor. /// public static string SettingsHoverActions_Description => Resources.GetTranslatedString(@"SettingsHoverActions_Description"); - /// /// Hover Actions /// public static string SettingsHoverActions_Title => Resources.GetTranslatedString(@"SettingsHoverActions_Title"); - /// /// Change display language for Wino. /// public static string SettingsLanguage_Description => Resources.GetTranslatedString(@"SettingsLanguage_Description"); - /// /// Display Language /// public static string SettingsLanguage_Title => Resources.GetTranslatedString(@"SettingsLanguage_Title"); - /// /// Language & Time /// public static string SettingsLanguageTime_Title => Resources.GetTranslatedString(@"SettingsLanguageTime_Title"); - /// /// Wino display language, preferred time format. /// public static string SettingsLanguageTime_Description => Resources.GetTranslatedString(@"SettingsLanguageTime_Description"); - /// /// Categories /// public static string CategoriesFolderNameOverride => Resources.GetTranslatedString(@"CategoriesFolderNameOverride"); - /// /// More /// public static string MoreFolderNameOverride => Resources.GetTranslatedString(@"MoreFolderNameOverride"); - /// /// Settings /// public static string SettingsOptions_Title => Resources.GetTranslatedString(@"SettingsOptions_Title"); - /// /// Merge multiple accounts into one. See mails from one Inbox together. /// public static string SettingsLinkAccounts_Description => Resources.GetTranslatedString(@"SettingsLinkAccounts_Description"); - /// /// Create Linked Accounts /// public static string SettingsLinkAccounts_Title => Resources.GetTranslatedString(@"SettingsLinkAccounts_Title"); - /// /// Modify the current link with the new accounts. /// public static string SettingsLinkedAccountsSave_Description => Resources.GetTranslatedString(@"SettingsLinkedAccountsSave_Description"); - /// /// Save Changes /// public static string SettingsLinkedAccountsSave_Title => Resources.GetTranslatedString(@"SettingsLinkedAccountsSave_Title"); - /// /// Load images automatically /// public static string SettingsLoadImages_Title => Resources.GetTranslatedString(@"SettingsLoadImages_Title"); - /// /// Load styles automatically /// public static string SettingsLoadStyles_Title => Resources.GetTranslatedString(@"SettingsLoadStyles_Title"); - /// /// Adjust the spacing for listing mails. /// public static string SettingsMailSpacing_Description => Resources.GetTranslatedString(@"SettingsMailSpacing_Description"); - /// /// Mail Spacing /// public static string SettingsMailSpacing_Title => Resources.GetTranslatedString(@"SettingsMailSpacing_Title"); - /// /// Create Nested Folders /// public static string SettingsFolderMenuStyle_Title => Resources.GetTranslatedString(@"SettingsFolderMenuStyle_Title"); - /// /// Change whether account folders should be nested inside an account menu item or not. Toggle this off if you like the old menu system in Windows Mail /// public static string SettingsFolderMenuStyle_Description => Resources.GetTranslatedString(@"SettingsFolderMenuStyle_Description"); - /// /// Notifications, signatures, synchronization and other settings per account. /// public static string SettingsManageAccountSettings_Description => Resources.GetTranslatedString(@"SettingsManageAccountSettings_Description"); - /// /// Manage Account Settings /// public static string SettingsManageAccountSettings_Title => Resources.GetTranslatedString(@"SettingsManageAccountSettings_Title"); - /// /// Reorder Accounts /// public static string SettingsReorderAccounts_Title => Resources.GetTranslatedString(@"SettingsReorderAccounts_Title"); - /// /// Change the order of accounts in the account list. /// public static string SettingsReorderAccounts_Description => Resources.GetTranslatedString(@"SettingsReorderAccounts_Description"); - /// /// Move items to add new link or remove existing link. /// public static string SettingsManageLink_Description => Resources.GetTranslatedString(@"SettingsManageLink_Description"); - /// /// Manage Link /// public static string SettingsManageLink_Title => Resources.GetTranslatedString(@"SettingsManageLink_Title"); - /// /// Change what should happen to the selected item. /// public static string SettingsMarkAsRead_Description => Resources.GetTranslatedString(@"SettingsMarkAsRead_Description"); - /// /// Don't automatically mark item as read /// public static string SettingsMarkAsRead_DontChange => Resources.GetTranslatedString(@"SettingsMarkAsRead_DontChange"); - /// /// Seconds to wait: /// public static string SettingsMarkAsRead_SecondsToWait => Resources.GetTranslatedString(@"SettingsMarkAsRead_SecondsToWait"); - /// /// When viewed in the reading pane /// public static string SettingsMarkAsRead_Timer => Resources.GetTranslatedString(@"SettingsMarkAsRead_Timer"); - /// /// Mark item as read /// public static string SettingsMarkAsRead_Title => Resources.GetTranslatedString(@"SettingsMarkAsRead_Title"); - /// /// When selected /// public static string SettingsMarkAsRead_WhenSelected => Resources.GetTranslatedString(@"SettingsMarkAsRead_WhenSelected"); - /// /// Change how your messages should be organized in mail list. /// public static string SettingsMessageList_Description => Resources.GetTranslatedString(@"SettingsMessageList_Description"); - /// /// Message List /// public static string SettingsMessageList_Title => Resources.GetTranslatedString(@"SettingsMessageList_Title"); - /// /// You didn't setup any accounts yet. /// public static string SettingsNoAccountSetupMessage => Resources.GetTranslatedString(@"SettingsNoAccountSetupMessage"); - /// /// Turn on or off notifications for this account. /// public static string SettingsNotifications_Description => Resources.GetTranslatedString(@"SettingsNotifications_Description"); - /// /// Notifications /// public static string SettingsNotifications_Title => Resources.GetTranslatedString(@"SettingsNotifications_Title"); - /// /// Change the width of the mail list. /// public static string SettingsPaneLength_Description => Resources.GetTranslatedString(@"SettingsPaneLength_Description"); - /// /// Mail List Pane Length /// public static string SettingsPaneLength_Title => Resources.GetTranslatedString(@"SettingsPaneLength_Title"); - /// /// Show much more love ❤️ All donations are appreciated. /// public static string SettingsPaypal_Description => Resources.GetTranslatedString(@"SettingsPaypal_Description"); - /// /// Donate via PayPal /// public static string SettingsPaypal_Title => Resources.GetTranslatedString(@"SettingsPaypal_Title"); - /// /// Compact Mode /// public static string SettingsPersonalizationMailDisplayCompactMode => Resources.GetTranslatedString(@"SettingsPersonalizationMailDisplayCompactMode"); - /// /// Medium Mode /// public static string SettingsPersonalizationMailDisplayMediumMode => Resources.GetTranslatedString(@"SettingsPersonalizationMailDisplayMediumMode"); - /// /// Spacious Mode /// public static string SettingsPersonalizationMailDisplaySpaciousMode => Resources.GetTranslatedString(@"SettingsPersonalizationMailDisplaySpaciousMode"); - /// /// Change appearance of Wino as you like. /// public static string SettingsPersonalization_Description => Resources.GetTranslatedString(@"SettingsPersonalization_Description"); - /// /// Personalization /// public static string SettingsPersonalization_Title => Resources.GetTranslatedString(@"SettingsPersonalization_Title"); - /// /// Review privacy policy. /// public static string SettingsPrivacyPolicy_Description => Resources.GetTranslatedString(@"SettingsPrivacyPolicy_Description"); - /// /// Privacy Policy /// public static string SettingsPrivacyPolicy_Title => Resources.GetTranslatedString(@"SettingsPrivacyPolicy_Title"); - /// /// Mail rendering options. /// public static string SettingsReadingPane_Description => Resources.GetTranslatedString(@"SettingsReadingPane_Description"); - /// /// Reading Pane /// public static string SettingsReadingPane_Title => Resources.GetTranslatedString(@"SettingsReadingPane_Title"); - /// /// Default Reader Font /// public static string SettingsReaderFont_Title => Resources.GetTranslatedString(@"SettingsReaderFont_Title"); - /// /// Change the default font family and font size for reading mails. /// public static string SettingsReaderFontFamily_Description => Resources.GetTranslatedString(@"SettingsReaderFontFamily_Description"); - /// /// Font Family /// public static string SettingsFontFamily_Title => Resources.GetTranslatedString(@"SettingsFontFamily_Title"); - /// /// Font Size /// public static string SettingsFontSize_Title => Resources.GetTranslatedString(@"SettingsFontSize_Title"); - /// /// Preview /// public static string SettingsFontPreview_Title => Resources.GetTranslatedString(@"SettingsFontPreview_Title"); - /// /// Default Composer Font /// public static string SettingsComposerFont_Title => Resources.GetTranslatedString(@"SettingsComposerFont_Title"); - /// /// Change the default font family and font size for composing mails. /// public static string SettingsComposerFontFamily_Description => Resources.GetTranslatedString(@"SettingsComposerFontFamily_Description"); - /// /// Change the display name of the linked accounts. /// public static string SettingsRenameMergeAccount_Description => Resources.GetTranslatedString(@"SettingsRenameMergeAccount_Description"); - /// /// Rename /// public static string SettingsRenameMergeAccount_Title => Resources.GetTranslatedString(@"SettingsRenameMergeAccount_Title"); - /// /// This will allow you to click on the headers in messages list and go to specific date /// public static string SettingsSemanticZoom_Description => Resources.GetTranslatedString(@"SettingsSemanticZoom_Description"); - /// /// Semantic Zoom for Date Headers /// public static string SettingsSemanticZoom_Title => Resources.GetTranslatedString(@"SettingsSemanticZoom_Title"); - /// /// Hide/show thepreview text. /// public static string SettingsShowPreviewText_Description => Resources.GetTranslatedString(@"SettingsShowPreviewText_Description"); - /// /// Show Preview Text /// public static string SettingsShowPreviewText_Title => Resources.GetTranslatedString(@"SettingsShowPreviewText_Title"); - /// /// Hide/show the thumbnail sender pictures. /// public static string SettingsShowSenderPictures_Description => Resources.GetTranslatedString(@"SettingsShowSenderPictures_Description"); - /// /// Show Sender Avatars /// public static string SettingsShowSenderPictures_Title => Resources.GetTranslatedString(@"SettingsShowSenderPictures_Title"); - /// /// Display Clock Format in 24 Hours /// public static string SettingsPrefer24HourClock_Title => Resources.GetTranslatedString(@"SettingsPrefer24HourClock_Title"); - /// /// Mail recieve times will be displayed in 24 hour format instead of 12 (AM/PM) /// public static string SettingsPrefer24HourClock_Description => Resources.GetTranslatedString(@"SettingsPrefer24HourClock_Description"); - /// /// Manage account signatures /// public static string SettingsSignature_Description => Resources.GetTranslatedString(@"SettingsSignature_Description"); - /// /// Signature /// public static string SettingsSignature_Title => Resources.GetTranslatedString(@"SettingsSignature_Title"); - /// /// Primary account item to load Inbox at startup. /// public static string SettingsStartupItem_Description => Resources.GetTranslatedString(@"SettingsStartupItem_Description"); - /// /// Startup Item /// public static string SettingsStartupItem_Title => Resources.GetTranslatedString(@"SettingsStartupItem_Title"); - /// /// Show some love ❤️ /// public static string SettingsStore_Description => Resources.GetTranslatedString(@"SettingsStore_Description"); - /// /// Rate in Store /// public static string SettingsStore_Title => Resources.GetTranslatedString(@"SettingsStore_Title"); - /// /// Organize messages into conversation threads. /// public static string SettingsThreads_Description => Resources.GetTranslatedString(@"SettingsThreads_Description"); - /// /// Conversation Threading /// public static string SettingsThreads_Title => Resources.GetTranslatedString(@"SettingsThreads_Title"); - /// /// Remove the link between accounts. This will not delete your accounts. /// public static string SettingsUnlinkAccounts_Description => Resources.GetTranslatedString(@"SettingsUnlinkAccounts_Description"); - /// /// Unlink Accounts /// public static string SettingsUnlinkAccounts_Title => Resources.GetTranslatedString(@"SettingsUnlinkAccounts_Title"); - /// /// by date /// public static string SortingOption_Date => Resources.GetTranslatedString(@"SortingOption_Date"); - /// /// by name /// public static string SortingOption_Name => Resources.GetTranslatedString(@"SortingOption_Name"); - /// /// All feedbacks are appreciated and they will make much Wino better in the future. Would you like to rate Wino in Microsoft Store? /// public static string StoreRatingDialog_MessageFirstLine => Resources.GetTranslatedString(@"StoreRatingDialog_MessageFirstLine"); - /// /// Would you like to rate Wino Mail in Microsoft Store? /// public static string StoreRatingDialog_MessageSecondLine => Resources.GetTranslatedString(@"StoreRatingDialog_MessageSecondLine"); - /// /// Enjoying Wino? /// public static string StoreRatingDialog_Title => Resources.GetTranslatedString(@"StoreRatingDialog_Title"); - /// /// Archived messages will be moved to here. /// public static string SystemFolderConfigDialog_ArchiveFolderDescription => Resources.GetTranslatedString(@"SystemFolderConfigDialog_ArchiveFolderDescription"); - /// /// Archive Folder /// public static string SystemFolderConfigDialog_ArchiveFolderHeader => Resources.GetTranslatedString(@"SystemFolderConfigDialog_ArchiveFolderHeader"); - /// /// Deleted messages will be moved to here. /// public static string SystemFolderConfigDialog_DeletedFolderDescription => Resources.GetTranslatedString(@"SystemFolderConfigDialog_DeletedFolderDescription"); - /// /// Deleted Folder /// public static string SystemFolderConfigDialog_DeletedFolderHeader => Resources.GetTranslatedString(@"SystemFolderConfigDialog_DeletedFolderHeader"); - /// /// New mails/replies will be crafted in here. /// public static string SystemFolderConfigDialog_DraftFolderDescription => Resources.GetTranslatedString(@"SystemFolderConfigDialog_DraftFolderDescription"); - /// /// Draft Folder /// public static string SystemFolderConfigDialog_DraftFolderHeader => Resources.GetTranslatedString(@"SystemFolderConfigDialog_DraftFolderHeader"); - /// /// All spam/junk mails will be here. /// public static string SystemFolderConfigDialog_JunkFolderDescription => Resources.GetTranslatedString(@"SystemFolderConfigDialog_JunkFolderDescription"); - /// /// Junk/Spam Folder /// public static string SystemFolderConfigDialog_JunkFolderHeader => Resources.GetTranslatedString(@"SystemFolderConfigDialog_JunkFolderHeader"); - /// /// This IMAP server doesn't support SPECIAL-USE extension hence Wino couldn't setup the system folders properly. /// public static string SystemFolderConfigDialog_MessageFirstLine => Resources.GetTranslatedString(@"SystemFolderConfigDialog_MessageFirstLine"); - /// /// Please select the appropriate folders for specific functionalities. /// public static string SystemFolderConfigDialog_MessageSecondLine => Resources.GetTranslatedString(@"SystemFolderConfigDialog_MessageSecondLine"); - /// /// Folder that sent messages will be stored. /// public static string SystemFolderConfigDialog_SentFolderDescription => Resources.GetTranslatedString(@"SystemFolderConfigDialog_SentFolderDescription"); - /// /// Sent Folder /// public static string SystemFolderConfigDialog_SentFolderHeader => Resources.GetTranslatedString(@"SystemFolderConfigDialog_SentFolderHeader"); - /// /// Configure System Folders /// public static string SystemFolderConfigDialog_Title => Resources.GetTranslatedString(@"SystemFolderConfigDialog_Title"); - /// /// You can't assign Inbox folder to any other system folder. /// public static string SystemFolderConfigDialogValidation_InboxSelected => Resources.GetTranslatedString(@"SystemFolderConfigDialogValidation_InboxSelected"); - /// /// Some of the system folders are used more than once in the configuration. /// public static string SystemFolderConfigDialogValidation_DuplicateSystemFolders => Resources.GetTranslatedString(@"SystemFolderConfigDialogValidation_DuplicateSystemFolders"); - /// /// System Folders Setup /// public static string SystemFolderConfigSetupSuccess_Title => Resources.GetTranslatedString(@"SystemFolderConfigSetupSuccess_Title"); - /// /// System folders are successfully configured. /// public static string SystemFolderConfigSetupSuccess_Message => Resources.GetTranslatedString(@"SystemFolderConfigSetupSuccess_Message"); - /// /// Testing server connection... /// public static string TestingImapConnectionMessage => Resources.GetTranslatedString(@"TestingImapConnectionMessage"); - /// /// Today /// public static string Today => Resources.GetTranslatedString(@"Today"); - /// /// unknown address /// public static string UnknownAddress => Resources.GetTranslatedString(@"UnknownAddress"); - /// /// Unknown Date /// public static string UnknownDateHeader => Resources.GetTranslatedString(@"UnknownDateHeader"); - /// /// unknown Mail Group Address /// public static string UnknownGroupAddress => Resources.GetTranslatedString(@"UnknownGroupAddress"); - /// /// Unknown Sender /// public static string UnknownSender => Resources.GetTranslatedString(@"UnknownSender"); - /// /// Unsubscribe /// public static string Unsubscribe => Resources.GetTranslatedString(@"Unsubscribe"); - /// /// View Details /// public static string ViewContactDetails => Resources.GetTranslatedString(@"ViewContactDetails"); - /// /// Wino offers 3 accounts to start with for free. If you need more than 3 accounts, please upgrade /// public static string WinoUpgradeDescription => Resources.GetTranslatedString(@"WinoUpgradeDescription"); - /// /// Upgrade to Unlimited Accounts /// public static string WinoUpgradeMessage => Resources.GetTranslatedString(@"WinoUpgradeMessage"); - /// /// {0} out of {1} free accounts used. /// public static string WinoUpgradeRemainingAccountsMessage => Resources.GetTranslatedString(@"WinoUpgradeRemainingAccountsMessage"); - /// /// Yesterday /// public static string Yesterday => Resources.GetTranslatedString(@"Yesterday"); - /// /// Signature Editor /// public static string SignatureEditorDialog_Title => Resources.GetTranslatedString(@"SignatureEditorDialog_Title"); - /// /// Name your signature /// public static string SignatureEditorDialog_SignatureName_Placeholder => Resources.GetTranslatedString(@"SignatureEditorDialog_SignatureName_Placeholder"); - /// /// Signature name /// public static string SignatureEditorDialog_SignatureName_TitleNew => Resources.GetTranslatedString(@"SignatureEditorDialog_SignatureName_TitleNew"); - /// /// Current signature name: {0} /// public static string SignatureEditorDialog_SignatureName_TitleEdit => Resources.GetTranslatedString(@"SignatureEditorDialog_SignatureName_TitleEdit"); - /// /// Delete signature /// public static string SignatureDeleteDialog_Title => Resources.GetTranslatedString(@"SignatureDeleteDialog_Title"); - /// /// Are you sure you want to delete "{0}" signature? /// public static string SignatureDeleteDialog_Message => Resources.GetTranslatedString(@"SignatureDeleteDialog_Message"); - /// /// For New Messages /// public static string SettingsSignature_ForNewMessages_Title => Resources.GetTranslatedString(@"SettingsSignature_ForNewMessages_Title"); - /// /// For Replies/Forwards /// public static string SettingsSignature_ForFollowingMessages_Title => Resources.GetTranslatedString(@"SettingsSignature_ForFollowingMessages_Title"); - /// /// Signature defaults /// public static string SettingsSignature_SignatureDefaults => Resources.GetTranslatedString(@"SettingsSignature_SignatureDefaults"); - /// /// Signatures /// public static string SettingsSignature_Signatures => Resources.GetTranslatedString(@"SettingsSignature_Signatures"); - /// /// Add custom signature /// public static string SettingsSignature_AddCustomSignature_Title => Resources.GetTranslatedString(@"SettingsSignature_AddCustomSignature_Title"); - /// /// Add signature /// public static string SettingsSignature_AddCustomSignature_Button => Resources.GetTranslatedString(@"SettingsSignature_AddCustomSignature_Button"); - /// /// Edit signature /// public static string SettingsSignature_EditSignature_Title => Resources.GetTranslatedString(@"SettingsSignature_EditSignature_Title"); - /// /// Delete signature /// public static string SettingsSignature_DeleteSignature_Title => Resources.GetTranslatedString(@"SettingsSignature_DeleteSignature_Title"); - /// /// None /// public static string SettingsSignature_NoneSignatureName => Resources.GetTranslatedString(@"SettingsSignature_NoneSignatureName"); - -} + } } diff --git a/Wino.Core/Extensions/FolderTreeExtensions.cs b/Wino.Core/Extensions/FolderTreeExtensions.cs index 12c46447..18cf16ca 100644 --- a/Wino.Core/Extensions/FolderTreeExtensions.cs +++ b/Wino.Core/Extensions/FolderTreeExtensions.cs @@ -1,5 +1,4 @@ -using System.Linq; -using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Folders; using Wino.Core.MenuItems; @@ -7,31 +6,9 @@ namespace Wino.Core.Extensions { public static class FolderTreeExtensions { - public static AccountMenuItem GetAccountMenuTree(this AccountFolderTree accountTree, IMenuItem parentMenuItem = null) - { - var accountMenuItem = new AccountMenuItem(accountTree.Account, parentMenuItem); - - foreach (var structure in accountTree.Folders) - { - var tree = GetMenuItemByFolderRecursive(structure, accountMenuItem, null); - - accountMenuItem.SubMenuItems.Add(tree); - } - - - // Create flat folder hierarchy for ease of access. - accountMenuItem.FlattenedFolderHierarchy = ListExtensions - .FlattenBy(accountMenuItem.SubMenuItems, a => a.SubMenuItems) - .Where(a => a is FolderMenuItem) - .Cast() - .ToList(); - - return accountMenuItem; - } - private static MenuItemBase GetMenuItemByFolderRecursive(IMailItemFolder structure, AccountMenuItem parentAccountMenuItem, IMenuItem parentFolderItem) { - MenuItemBase parentMenuItem = new FolderMenuItem(structure, parentAccountMenuItem.Parameter, parentAccountMenuItem); + MenuItemBase parentMenuItem = new FolderMenuItem(structure, parentAccountMenuItem.Parameter, parentFolderItem); var childStructures = structure.ChildFolders; diff --git a/Wino.Core/Extensions/GoogleIntegratorExtensions.cs b/Wino.Core/Extensions/GoogleIntegratorExtensions.cs index 89a3c8f5..d03877b9 100644 --- a/Wino.Core/Extensions/GoogleIntegratorExtensions.cs +++ b/Wino.Core/Extensions/GoogleIntegratorExtensions.cs @@ -18,57 +18,114 @@ namespace Wino.Core.Extensions public const string STARRED_LABEL_ID = "STARRED"; public const string DRAFT_LABEL_ID = "DRAFT"; public const string SENT_LABEL_ID = "SENT"; + public const string SPAM_LABEL_ID = "SPAM"; + public const string CHAT_LABEL_ID = "CHAT"; + public const string TRASH_LABEL_ID = "TRASH"; + // Category labels. + public const string FORUMS_LABEL_ID = "FORUMS"; + public const string UPDATES_LABEL_ID = "UPDATES"; + public const string PROMOTIONS_LABEL_ID = "PROMOTIONS"; + public const string SOCIAL_LABEL_ID = "SOCIAL"; + public const string PERSONAL_LABEL_ID = "PERSONAL"; + + // Label visibility identifiers. private const string SYSTEM_FOLDER_IDENTIFIER = "system"; private const string FOLDER_HIDE_IDENTIFIER = "labelHide"; - private static Dictionary KnownFolderDictioanry = new Dictionary() + private const string CATEGORY_PREFIX = "CATEGORY_"; + private const string FOLDER_SEPERATOR_STRING = "/"; + private const char FOLDER_SEPERATOR_CHAR = '/'; + + private static Dictionary KnownFolderDictionary = new Dictionary() { { INBOX_LABEL_ID, SpecialFolderType.Inbox }, - { "CHAT", SpecialFolderType.Chat }, + { CHAT_LABEL_ID, SpecialFolderType.Chat }, { IMPORTANT_LABEL_ID, SpecialFolderType.Important }, - { "TRASH", SpecialFolderType.Deleted }, + { TRASH_LABEL_ID, SpecialFolderType.Deleted }, { DRAFT_LABEL_ID, SpecialFolderType.Draft }, { SENT_LABEL_ID, SpecialFolderType.Sent }, - { "SPAM", SpecialFolderType.Junk }, + { SPAM_LABEL_ID, SpecialFolderType.Junk }, { STARRED_LABEL_ID, SpecialFolderType.Starred }, { UNREAD_LABEL_ID, SpecialFolderType.Unread }, - { "FORUMS", SpecialFolderType.Forums }, - { "UPDATES", SpecialFolderType.Updates }, - { "PROMOTIONS", SpecialFolderType.Promotions }, - { "SOCIAL", SpecialFolderType.Social}, - { "PERSONAL", SpecialFolderType.Personal}, + { FORUMS_LABEL_ID, SpecialFolderType.Forums }, + { UPDATES_LABEL_ID, SpecialFolderType.Updates }, + { PROMOTIONS_LABEL_ID, SpecialFolderType.Promotions }, + { SOCIAL_LABEL_ID, SpecialFolderType.Social}, + { PERSONAL_LABEL_ID, SpecialFolderType.Personal}, }; - public static MailItemFolder GetLocalFolder(this Label label, Guid accountId) + public static string[] SubCategoryFolderLabelIds = + [ + FORUMS_LABEL_ID, + UPDATES_LABEL_ID, + PROMOTIONS_LABEL_ID, + SOCIAL_LABEL_ID, + PERSONAL_LABEL_ID + ]; + + private static string GetNormalizedLabelName(string labelName) { - var unchangedFolderName = label.Name; + // 1. Remove CATEGORY_ prefix. + var normalizedLabelName = labelName.Replace(CATEGORY_PREFIX, string.Empty); - if (label.Name.StartsWith("CATEGORY_")) - label.Name = label.Name.Replace("CATEGORY_", ""); + // 2. Normalize label name by capitalizing first letter. + normalizedLabelName = char.ToUpper(normalizedLabelName[0]) + normalizedLabelName.Substring(1).ToLower(); - bool isSpecialFolder = KnownFolderDictioanry.ContainsKey(label.Name); + return normalizedLabelName; + } + + public static MailItemFolder GetLocalFolder(this Label label, ListLabelsResponse labelsResponse, Guid accountId) + { bool isAllCapital = label.Name.All(a => char.IsUpper(a)); - var specialFolderType = isSpecialFolder ? KnownFolderDictioanry[label.Name] : SpecialFolderType.Other; + var normalizedLabelName = GetFolderName(label); - return new MailItemFolder() + // Even though we normalize the label name, check is done by capitalizing the label name. + var capitalNormalizedLabelName = normalizedLabelName.ToUpper(); + + bool isSpecialFolder = KnownFolderDictionary.ContainsKey(capitalNormalizedLabelName); + + var specialFolderType = isSpecialFolder ? KnownFolderDictionary[capitalNormalizedLabelName] : SpecialFolderType.Other; + + // We used to support FOLDER_HIDE_IDENTIFIER to hide invisible folders. + // However, a lot of people complained that they don't see their folders after the initial sync + // without realizing that they are hidden in Gmail settings. Therefore, it makes more sense to ignore Gmail's configuration + // since Wino allows folder visibility configuration separately. + + // Overridden hidden labels are shown in the UI, but they have their synchronization disabled. + // This is mainly because 'All Mails' label is hidden by default in Gmail, but there is no point to download all mails. + + bool shouldEnableSynchronization = label.LabelListVisibility != FOLDER_HIDE_IDENTIFIER; + bool isHidden = false; + + bool isChildOfCategoryFolder = label.Name.StartsWith(CATEGORY_PREFIX); + bool isSticky = isSpecialFolder && specialFolderType != SpecialFolderType.Category && !isChildOfCategoryFolder; + + // By default, all special folders update unread count in the UI except Trash. + bool shouldShowUnreadCount = specialFolderType != SpecialFolderType.Deleted || specialFolderType != SpecialFolderType.Other; + + bool isSystemFolder = label.Type == SYSTEM_FOLDER_IDENTIFIER; + + var localFolder = new MailItemFolder() { TextColorHex = label.Color?.TextColor, BackgroundColorHex = label.Color?.BackgroundColor, - FolderName = isAllCapital ? char.ToUpper(label.Name[0]) + label.Name.Substring(1).ToLower() : label.Name, // Capitilize only first letter. + FolderName = normalizedLabelName, RemoteFolderId = label.Id, Id = Guid.NewGuid(), MailAccountId = accountId, - IsSynchronizationEnabled = true, + IsSynchronizationEnabled = shouldEnableSynchronization, SpecialFolderType = specialFolderType, - IsSystemFolder = label.Type == SYSTEM_FOLDER_IDENTIFIER, - IsSticky = isSpecialFolder && specialFolderType != SpecialFolderType.Category && !unchangedFolderName.StartsWith("CATEGORY"), - IsHidden = label.LabelListVisibility == FOLDER_HIDE_IDENTIFIER, - - // By default, all special folders update unread count in the UI except Trash. - ShowUnreadCount = specialFolderType != SpecialFolderType.Deleted || specialFolderType != SpecialFolderType.Other + IsSystemFolder = isSystemFolder, + IsSticky = isSticky, + IsHidden = isHidden, + ShowUnreadCount = shouldShowUnreadCount, }; + + localFolder.ParentRemoteFolderId = isChildOfCategoryFolder ? string.Empty : GetParentFolderRemoteId(label.Name, labelsResponse); + + return localFolder; } public static bool GetIsDraft(this Message message) @@ -83,6 +140,36 @@ namespace Wino.Core.Extensions public static bool GetIsFlagged(this Message message) => message?.LabelIds?.Any(a => a == STARRED_LABEL_ID) ?? false; + private static string GetParentFolderRemoteId(string fullLabelName, ListLabelsResponse labelsResponse) + { + if (string.IsNullOrEmpty(fullLabelName)) return string.Empty; + + // Find the last index of '/' + int lastIndex = fullLabelName.LastIndexOf('/'); + + // If '/' not found or it's at the start, return the empty string. + if (lastIndex <= 0) return string.Empty; + + // Extract the parent label + var parentLabelName = fullLabelName.Substring(0, lastIndex); + + return labelsResponse.Labels.FirstOrDefault(a => a.Name == parentLabelName)?.Id ?? string.Empty; + } + + public static string GetFolderName(Label label) + { + if (string.IsNullOrEmpty(label.Name)) return string.Empty; + + // Folders with "//" at the end has "/" as the name. + if (label.Name.EndsWith(FOLDER_SEPERATOR_STRING)) return FOLDER_SEPERATOR_STRING; + + string[] parts = label.Name.Split(FOLDER_SEPERATOR_CHAR); + + var lastPart = parts[parts.Length - 1]; + + return GetNormalizedLabelName(lastPart); + } + /// /// Returns MailCopy out of native Gmail message and converted MimeMessage of that native messaage. /// @@ -157,6 +244,5 @@ namespace Wino.Core.Extensions return new Tuple>(mailCopy, mimeMessage, message.LabelIds); } - } } diff --git a/Wino.Core/Integration/BaseMailIntegrator.cs b/Wino.Core/Integration/BaseMailIntegrator.cs index a4bda8ea..decab393 100644 --- a/Wino.Core/Integration/BaseMailIntegrator.cs +++ b/Wino.Core/Integration/BaseMailIntegrator.cs @@ -97,6 +97,13 @@ namespace Wino.Core.Integration yield return new HttpRequestBundle(action(item), batchChangeRequest); } + public IEnumerable> CreateHttpBundleWithResponse( + IRequestBase item, + Func action) + { + yield return new HttpRequestBundle(action(item), item); + } + /// /// Creates a batched HttpBundle with TResponse of expected response type from the http call for each of the items in the batch. /// Func will be executed for each item separately in the batch request. diff --git a/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs b/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs index 69709bd0..990856eb 100644 --- a/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs +++ b/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs @@ -28,12 +28,23 @@ namespace Wino.Core.Integration.Processors Task DeleteMailAsync(Guid accountId, string mailId); Task> GetDownloadedUnreadMailsAsync(Guid accountId, IEnumerable downloadedMailCopyIds); Task SaveMimeFileAsync(Guid fileId, MimeMessage mimeMessage, Guid accountId); - Task UpdateFolderStructureAsync(Guid accountId, List allFolders); Task DeleteFolderAsync(Guid accountId, string remoteFolderId); - Task> GetSynchronizationFoldersAsync(SynchronizationOptions options); Task InsertFolderAsync(MailItemFolder folder); + Task UpdateFolderAsync(MailItemFolder folder); + + /// + /// Returns the list of folders that are available for account. + /// + /// Account id to get folders for. + /// All folders. + Task> GetLocalFoldersAsync(Guid accountId); + + Task> GetSynchronizationFoldersAsync(SynchronizationOptions options); + Task MapLocalDraftAsync(Guid accountId, Guid localDraftCopyUniqueId, string newMailCopyId, string newDraftId, string newThreadId); Task UpdateFolderLastSyncDateAsync(Guid folderId); + + Task> GetExistingFoldersAsync(Guid accountId); } public interface IGmailChangeProcessor : IDefaultChangeProcessor @@ -68,19 +79,6 @@ namespace Wino.Core.Integration.Processors /// /// Folder id to retrieve uIds for. Task> GetKnownUidsForFolderAsync(Guid folderId); - - /// - /// Returns the list of folders that are available for account. - /// - /// Account id to get folders for. - /// All folders. - Task> GetLocalIMAPFoldersAsync(Guid accountId); - - /// - /// Updates folder. - /// - /// Folder to update. - Task UpdateFolderAsync(MailItemFolder folder); } public class DefaultChangeProcessor(IDatabaseService databaseService, @@ -116,14 +114,14 @@ namespace Wino.Core.Integration.Processors public Task CreateMailAsync(Guid accountId, NewMailItemPackage package) => MailService.CreateMailAsync(accountId, package); - // Folder methods - public Task UpdateFolderStructureAsync(Guid accountId, List allFolders) - => FolderService.BulkUpdateFolderStructureAsync(accountId, allFolders); + public Task> GetExistingFoldersAsync(Guid accountId) + => FolderService.GetFoldersAsync(accountId); public Task MapLocalDraftAsync(Guid accountId, Guid localDraftCopyUniqueId, string newMailCopyId, string newDraftId, string newThreadId) => MailService.MapLocalDraftAsync(accountId, localDraftCopyUniqueId, newMailCopyId, newDraftId, newThreadId); - + public Task> GetLocalFoldersAsync(Guid accountId) + => FolderService.GetFoldersAsync(accountId); public Task> GetSynchronizationFoldersAsync(SynchronizationOptions options) => FolderService.GetSynchronizationFoldersAsync(options); @@ -134,6 +132,9 @@ namespace Wino.Core.Integration.Processors public Task InsertFolderAsync(MailItemFolder folder) => FolderService.InsertFolderAsync(folder); + public Task UpdateFolderAsync(MailItemFolder folder) + => FolderService.UpdateFolderAsync(folder); + public Task> GetDownloadedUnreadMailsAsync(Guid accountId, IEnumerable downloadedMailCopyIds) => MailService.GetDownloadedUnreadMailsAsync(accountId, downloadedMailCopyIds); diff --git a/Wino.Core/Integration/Processors/ImapChangeProcessor.cs b/Wino.Core/Integration/Processors/ImapChangeProcessor.cs index d1c0d98c..e394514e 100644 --- a/Wino.Core/Integration/Processors/ImapChangeProcessor.cs +++ b/Wino.Core/Integration/Processors/ImapChangeProcessor.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Wino.Core.Domain.Entities; using Wino.Core.Domain.Interfaces; using Wino.Core.Services; @@ -17,13 +16,6 @@ namespace Wino.Core.Integration.Processors { } - public Task> GetKnownUidsForFolderAsync(Guid folderId) - => FolderService.GetKnownUidsForFolderAsync(folderId); - - public Task> GetLocalIMAPFoldersAsync(Guid accountId) - => FolderService.GetFoldersAsync(accountId); - - public Task UpdateFolderAsync(MailItemFolder folder) - => FolderService.UpdateFolderAsync(folder); + public Task> GetKnownUidsForFolderAsync(Guid folderId) => FolderService.GetKnownUidsForFolderAsync(folderId); } } diff --git a/Wino.Core/MenuItems/AccountMenuItem.cs b/Wino.Core/MenuItems/AccountMenuItem.cs index 8ebbb799..045038f4 100644 --- a/Wino.Core/MenuItems/AccountMenuItem.cs +++ b/Wino.Core/MenuItems/AccountMenuItem.cs @@ -11,8 +11,6 @@ namespace Wino.Core.MenuItems { public partial class AccountMenuItem : MenuItemBase>, IAccountMenuItem { - public List FlattenedFolderHierarchy { get; set; } - [ObservableProperty] private int unreadItemCount; @@ -20,6 +18,9 @@ namespace Wino.Core.MenuItems [NotifyPropertyChangedFor(nameof(IsSynchronizationProgressVisible))] private double synchronizationProgress; + [ObservableProperty] + private bool _isEnabled = true; + public bool IsAttentionRequired => AttentionReason != AccountAttentionReason.None; public bool IsSynchronizationProgressVisible => SynchronizationProgress > 0 && SynchronizationProgress < 100; public Guid AccountId => Parameter.Id; @@ -88,8 +89,5 @@ namespace Wino.Core.MenuItems } } } - - public int GetUnreadItemCountByFolderType(SpecialFolderType specialFolderType) - => FlattenedFolderHierarchy?.Where(a => a.SpecialFolderType == specialFolderType).Sum(a => a.UnreadItemCount) ?? 0; } } diff --git a/Wino.Core/MenuItems/FolderMenuItem.cs b/Wino.Core/MenuItems/FolderMenuItem.cs index 5013794f..90b3ffbe 100644 --- a/Wino.Core/MenuItems/FolderMenuItem.cs +++ b/Wino.Core/MenuItems/FolderMenuItem.cs @@ -52,6 +52,8 @@ namespace Wino.Core.MenuItems public bool ShowUnreadCount => Parameter.ShowUnreadCount; + IEnumerable IBaseFolderMenuItem.SubMenuItems => SubMenuItems; + public FolderMenuItem(IMailItemFolder folderStructure, MailAccount parentAccount, IMenuItem parentMenuItem) : base(folderStructure, folderStructure.Id, parentMenuItem) { ParentAccount = parentAccount; diff --git a/Wino.Core/MenuItems/MenuItemCollection.cs b/Wino.Core/MenuItems/MenuItemCollection.cs index f166a08a..f1357d35 100644 --- a/Wino.Core/MenuItems/MenuItemCollection.cs +++ b/Wino.Core/MenuItems/MenuItemCollection.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; +using MoreLinq.Extensions; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; @@ -8,64 +10,63 @@ namespace Wino.Core.MenuItems { public class MenuItemCollection : ObservableRangeCollection { - public IEnumerable GetFolderItems(Guid folderId) + // Which types to remove from the list when folders are changing due to selection of new account. + // We don't clear the whole list since we want to keep the New Mail button and account menu items. + private readonly Type[] _preservingTypesForFolderArea = [typeof(AccountMenuItem), typeof(NewMailMenuItem), typeof(MergedAccountMenuItem)]; + private readonly IDispatcher _dispatcher; + + public MenuItemCollection(IDispatcher dispatcher) { - var rootItems = this.OfType() - .SelectMany(a => a.FlattenedFolderHierarchy) - .Where(a => a.Parameter?.Id == folderId) - .Cast(); - - // Accounts that are merged can't exist in the root items. - // Therefore if the folder is found in root items, return it without searching inside merged accounts. - - if (rootItems.Any()) return rootItems; - - var mergedItems = this.OfType() - .SelectMany(a => a.SubMenuItems.OfType() - .Where(a => a.Parameter.Any(b => b.Id == folderId))) - .Cast(); - - // Folder is found in the MergedInbox shared folders. - if (mergedItems.Any()) return mergedItems; - - // Folder is not in any of the above. Looks inside the individual accounts in merged inbox account menu item. - var mergedAccountItems = this.OfType() - .SelectMany(a => a.SubMenuItems.OfType() - .SelectMany(a => a.FlattenedFolderHierarchy) - .Where(a => a.Parameter?.Id == folderId)) - .Cast(); - - return mergedAccountItems; + _dispatcher = dispatcher; } - public IBaseFolderMenuItem GetFolderItem(Guid folderId) => GetFolderItems(folderId).FirstOrDefault(); - - public IAccountMenuItem GetAccountMenuItem(Guid accountId) + public IEnumerable GetAllAccountMenuItems() { - if (accountId == null) return null; + foreach (var item in this) + { + if (item is MergedAccountMenuItem mergedAccountMenuItem) + { + foreach (var singleItem in mergedAccountMenuItem.SubMenuItems.OfType()) + { + yield return singleItem; + } - if (TryGetRootAccountMenuItem(accountId, out IAccountMenuItem rootAccountMenuItem)) return rootAccountMenuItem; - - return null; + yield return mergedAccountMenuItem; + } + else if (item is IAccountMenuItem accountMenuItem) + yield return accountMenuItem; + } } - // Pattern: Look for root account menu item only. Don't search inside the merged account menu item. - public bool TryGetRootAccountMenuItem(Guid accountId, out IAccountMenuItem value) + public IEnumerable GetAllFolderMenuItems(Guid folderId) { - value = this.OfType().FirstOrDefault(a => a.HoldingAccounts.Any(b => b.Id == accountId)); + foreach (var item in this) + { + if (item is IBaseFolderMenuItem folderMenuItem) + { + if (folderMenuItem.HandlingFolders.Any(a => a.Id == folderId)) + { + yield return folderMenuItem; + } + else if (folderMenuItem.SubMenuItems.Any()) + { + foreach (var subItem in folderMenuItem.SubMenuItems.OfType()) + { + if (subItem.HandlingFolders.Any(a => a.Id == folderId)) + { + yield return subItem; + } + } - value ??= this.OfType().FirstOrDefault(a => a.EntityId == accountId); - - return value != null; + } + } + } } - // Pattern: Look for root account menu item only and return the folder menu item inside the account menu item that has specific special folder type. - public bool TryGetRootSpecialFolderMenuItem(Guid accountId, SpecialFolderType specialFolderType, out FolderMenuItem value) + public bool TryGetAccountMenuItem(Guid accountId, out IAccountMenuItem value) { - value = this.OfType() - .Where(a => a.HoldingAccounts.Any(b => b.Id == accountId)) - .SelectMany(a => a.FlattenedFolderHierarchy) - .FirstOrDefault(a => a.Parameter?.SpecialFolderType == specialFolderType); + value = this.OfType().FirstOrDefault(a => a.AccountId == accountId); + value ??= this.OfType().FirstOrDefault(a => a.SubMenuItems.OfType().Where(b => b.AccountId == accountId) != null); return value != null; } @@ -83,37 +84,49 @@ namespace Wino.Core.MenuItems // This will not look for the folders inside individual account menu items inside merged account menu item. public bool TryGetMergedAccountSpecialFolderMenuItem(Guid mergedInboxId, SpecialFolderType specialFolderType, out IBaseFolderMenuItem value) { - value = this.OfType() - .Where(a => a.EntityId == mergedInboxId) - .SelectMany(a => a.SubMenuItems) - .OfType() + value = this.OfType() + .Where(a => a.MergedInbox.Id == mergedInboxId) .FirstOrDefault(a => a.SpecialFolderType == specialFolderType); return value != null; } - // Pattern: Find the child account menu item inside the merged account menu item, locate the special folder menu item inside the child account menu item. - public bool TryGetMergedAccountFolderMenuItemByAccountId(Guid accountId, SpecialFolderType specialFolderType, out FolderMenuItem value) + public bool TryGetFolderMenuItem(Guid folderId, out IBaseFolderMenuItem value) { - value = this.OfType() - .SelectMany(a => a.SubMenuItems) - .OfType() - .FirstOrDefault(a => a.HoldingAccounts.Any(b => b.Id == accountId)) - ?.FlattenedFolderHierarchy - .OfType() - .FirstOrDefault(a => a.Parameter?.SpecialFolderType == specialFolderType); + // Root folders + value = this.OfType() + .FirstOrDefault(a => a.HandlingFolders.Any(b => b.Id == folderId)); + + value ??= this.OfType() + .SelectMany(a => a.SubMenuItems) + .OfType() + .FirstOrDefault(a => a.HandlingFolders.Any(b => b.Id == folderId)); return value != null; } - // Pattern: Find the common folder menu item with special folder type inside the merged account menu item for the given AccountId. - public bool TryGetMergedAccountRootFolderMenuItemByAccountId(Guid accountId, SpecialFolderType specialFolderType, out MergedAccountFolderMenuItem value) + public void UpdateUnreadItemCountsToZero() { - value = this.OfType() - .Where(a => a.HoldingAccounts.Any(b => b.Id == accountId)) - .SelectMany(a => a.SubMenuItems) - .OfType() - .FirstOrDefault(a => a.SpecialFolderType == specialFolderType); + // Handle the root folders. + this.OfType().ForEach(a => RecursivelyResetUnreadItemCount(a)); + } + + private void RecursivelyResetUnreadItemCount(IBaseFolderMenuItem baseFolderMenuItem) + { + baseFolderMenuItem.UnreadItemCount = 0; + + if (baseFolderMenuItem.SubMenuItems == null) return; + + foreach (var subMenuItem in baseFolderMenuItem.SubMenuItems.OfType()) + { + RecursivelyResetUnreadItemCount(subMenuItem); + } + } + + public bool TryGetSpecialFolderMenuItem(Guid accountId, SpecialFolderType specialFolderType, out FolderMenuItem value) + { + value = this.OfType() + .FirstOrDefault(a => a.HandlingFolders.Any(b => b.MailAccountId == accountId && b.SpecialFolderType == specialFolderType)) as FolderMenuItem; return value != null; } @@ -138,12 +151,29 @@ namespace Wino.Core.MenuItems return accountMenuItem; } - public void ReplaceFolders(IEnumerable folders) + public async Task ReplaceFoldersAsync(IEnumerable folders) { - ClearFolderAreaMenuItems(); + await _dispatcher.ExecuteOnUIThread(() => + { + ClearFolderAreaMenuItems(); - Items.Add(new SeperatorItem()); - AddRange(folders); + Items.Add(new SeperatorItem()); + AddRange(folders); + }); + } + + /// + /// Enables/disables account menu items in the list. + /// + /// Whether menu items should be enabled or disabled. + public async Task SetAccountMenuItemEnabledStatusAsync(bool isEnabled) + { + var accountItems = this.Where(a => a is IAccountMenuItem).Cast(); + + await _dispatcher.ExecuteOnUIThread(() => + { + accountItems.ForEach(a => a.IsEnabled = isEnabled); + }); } public void AddAccountMenuItem(IAccountMenuItem accountMenuItem) @@ -158,18 +188,15 @@ namespace Wino.Core.MenuItems private void ClearFolderAreaMenuItems() { - var cloneItems = Items.ToList(); + var itemsToRemove = this.Where(a => !_preservingTypesForFolderArea.Contains(a.GetType())).ToList(); - foreach (var item in cloneItems) + itemsToRemove.ForEach(item => { - if (item is SeperatorItem || item is IBaseFolderMenuItem || item is MergedAccountMoreFolderMenuItem) - { - item.IsSelected = false; - item.IsExpanded = false; + item.IsExpanded = false; + item.IsSelected = false; + }); - Remove(item); - } - } + RemoveRange(itemsToRemove); } } } diff --git a/Wino.Core/MenuItems/MergedAccountFolderMenuItem.cs b/Wino.Core/MenuItems/MergedAccountFolderMenuItem.cs index 4ad3601e..b1412504 100644 --- a/Wino.Core/MenuItems/MergedAccountFolderMenuItem.cs +++ b/Wino.Core/MenuItems/MergedAccountFolderMenuItem.cs @@ -37,6 +37,8 @@ namespace Wino.Core.MenuItems public bool ShowUnreadCount => HandlingFolders?.Any(a => a.ShowUnreadCount) ?? false; + public IEnumerable SubMenuItems => SubMenuItems; + [ObservableProperty] private int unreadItemCount; diff --git a/Wino.Core/MenuItems/MergedAccountMenuItem.cs b/Wino.Core/MenuItems/MergedAccountMenuItem.cs index 42058afe..842824ab 100644 --- a/Wino.Core/MenuItems/MergedAccountMenuItem.cs +++ b/Wino.Core/MenuItems/MergedAccountMenuItem.cs @@ -2,16 +2,15 @@ using System.Linq; using CommunityToolkit.Mvvm.ComponentModel; using Wino.Core.Domain.Entities; -using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; namespace Wino.Core.MenuItems { - public partial class MergedAccountMenuItem : MenuItemBase, IAccountMenuItem + public partial class MergedAccountMenuItem : MenuItemBase, IMergedAccountMenuItem { - public int MergedAccountCount => GetAccountMenuItems().Count(); + public int MergedAccountCount => HoldingAccounts?.Count() ?? 0; - public IEnumerable HoldingAccounts => GetAccountMenuItems()?.SelectMany(a => a.HoldingAccounts); + public IEnumerable HoldingAccounts { get; } [ObservableProperty] private int unreadItemCount; @@ -22,34 +21,23 @@ namespace Wino.Core.MenuItems [ObservableProperty] private string mergedAccountName; - public MergedAccountMenuItem(MergedInbox mergedInbox, IMenuItem parent) : base(mergedInbox, mergedInbox.Id, parent) + [ObservableProperty] + private bool _isEnabled = true; + + public MergedAccountMenuItem(MergedInbox mergedInbox, IEnumerable holdingAccounts, IMenuItem parent) : base(mergedInbox, mergedInbox.Id, parent) { MergedAccountName = mergedInbox.Name; + HoldingAccounts = holdingAccounts; } public void RefreshFolderItemCount() { - UnreadItemCount = GetAccountMenuItems().Select(a => a.GetUnreadItemCountByFolderType(SpecialFolderType.Inbox)).Sum(); - - var unreadUpdateFolders = SubMenuItems.OfType().Where(a => a.ShowUnreadCount); - - foreach (var folder in unreadUpdateFolders) - { - folder.UnreadItemCount = GetAccountMenuItems().Select(a => a.GetUnreadItemCountByFolderType(folder.SpecialFolderType)).Sum(); - } - } - - // Accounts are always located in More folder of Merged Inbox menu item. - public IEnumerable GetAccountMenuItems() - { - var moreFolder = SubMenuItems.OfType().FirstOrDefault(); - - if (moreFolder == null) return default; - - return moreFolder.SubMenuItems.OfType(); + UnreadItemCount = SubMenuItems.OfType().Sum(a => a.UnreadItemCount); } public void UpdateAccount(MailAccount account) - => GetAccountMenuItems().FirstOrDefault(a => a.HoldingAccounts.Any(b => b.Id == account.Id))?.UpdateAccount(account); + { + + } } } diff --git a/Wino.Core/Messages/Mails/DisposeRenderingFrameRequested.cs b/Wino.Core/Messages/Mails/DisposeRenderingFrameRequested.cs new file mode 100644 index 00000000..9de7562f --- /dev/null +++ b/Wino.Core/Messages/Mails/DisposeRenderingFrameRequested.cs @@ -0,0 +1,7 @@ +namespace Wino.Core.Messages.Mails +{ + /// + /// When rendering frame should be disposed. + /// + public class DisposeRenderingFrameRequested { } +} diff --git a/Wino.Core/Requests/EmptyFolderRequest.cs b/Wino.Core/Requests/EmptyFolderRequest.cs new file mode 100644 index 00000000..7adfdda6 --- /dev/null +++ b/Wino.Core/Requests/EmptyFolderRequest.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using CommunityToolkit.Mvvm.Messaging; +using Wino.Core.Domain.Entities; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Requests; + +namespace Wino.Core.Requests +{ + public record EmptyFolderRequest(MailItemFolder Folder, List MailsToDelete) : FolderRequestBase(Folder, MailSynchronizerOperation.EmptyFolder), ICustomFolderSynchronizationRequest + { + public override void ApplyUIChanges() + { + foreach (var item in MailsToDelete) + { + WeakReferenceMessenger.Default.Send(new MailRemovedMessage(item)); + } + } + + public override void RevertUIChanges() + { + foreach (var item in MailsToDelete) + { + WeakReferenceMessenger.Default.Send(new MailAddedMessage(item)); + } + } + + public List SynchronizationFolderIds => [Folder.Id]; + } +} diff --git a/Wino.Core/Requests/MarkFolderAsReadRequest.cs b/Wino.Core/Requests/MarkFolderAsReadRequest.cs new file mode 100644 index 00000000..31994ac2 --- /dev/null +++ b/Wino.Core/Requests/MarkFolderAsReadRequest.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using CommunityToolkit.Mvvm.Messaging; +using Wino.Core.Domain.Entities; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Requests; + +namespace Wino.Core.Requests +{ + public record MarkFolderAsReadRequest(MailItemFolder Folder, List MailsToMarkRead) : FolderRequestBase(Folder, MailSynchronizerOperation.MarkFolderRead), ICustomFolderSynchronizationRequest + { + public override void ApplyUIChanges() + { + foreach (var item in MailsToMarkRead) + { + item.IsRead = true; + + WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(item)); + } + } + + public override void RevertUIChanges() + { + foreach (var item in MailsToMarkRead) + { + item.IsRead = false; + + WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(item)); + } + } + + public override bool DelayExecution => false; + + public List SynchronizationFolderIds => [Folder.Id]; + } +} diff --git a/Wino.Core/Requests/RenameFolderRequest.cs b/Wino.Core/Requests/RenameFolderRequest.cs index 6b431101..13db1553 100644 --- a/Wino.Core/Requests/RenameFolderRequest.cs +++ b/Wino.Core/Requests/RenameFolderRequest.cs @@ -1,19 +1,22 @@ -using Wino.Core.Domain.Entities; +using CommunityToolkit.Mvvm.Messaging; +using Wino.Core.Domain.Entities; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Models.Requests; namespace Wino.Core.Requests { - public record RenameFolderRequest(MailItemFolder Folder) : FolderRequestBase(Folder, MailSynchronizerOperation.RenameFolder) + public record RenameFolderRequest(MailItemFolder Folder, string CurrentFolderName, string NewFolderName) : FolderRequestBase(Folder, MailSynchronizerOperation.RenameFolder) { public override void ApplyUIChanges() { - + Folder.FolderName = NewFolderName; + WeakReferenceMessenger.Default.Send(new FolderRenamed(Folder)); } public override void RevertUIChanges() { - + Folder.FolderName = CurrentFolderName; + WeakReferenceMessenger.Default.Send(new FolderRenamed(Folder)); } } } diff --git a/Wino.Core/Requests/SendDraftRequest.cs b/Wino.Core/Requests/SendDraftRequest.cs index 5d5758e5..5cace856 100644 --- a/Wino.Core/Requests/SendDraftRequest.cs +++ b/Wino.Core/Requests/SendDraftRequest.cs @@ -55,5 +55,7 @@ namespace Wino.Core.Requests { Items.ForEach(item => WeakReferenceMessenger.Default.Send(new MailAddedMessage(item.Item))); } + + public override bool DelayExecution => true; } } diff --git a/Wino.Core/Requests/UIMessages.cs b/Wino.Core/Requests/UIMessages.cs index f47c7644..6373a350 100644 --- a/Wino.Core/Requests/UIMessages.cs +++ b/Wino.Core/Requests/UIMessages.cs @@ -1,5 +1,6 @@ using System; using Wino.Core.Domain.Entities; +using Wino.Core.Domain.Models.Folders; using Wino.Core.Domain.Models.Requests; namespace Wino.Core.Requests @@ -9,10 +10,6 @@ namespace Wino.Core.Requests public record MailUpdatedMessage(MailCopy UpdatedMail) : IUIMessage; public record MailDownloadedMessage(MailCopy DownloadedMail) : IUIMessage; - public record FolderAddedMessage(MailItemFolder AddedFolder, MailAccount Account) : IUIMessage; - public record FolderRemovedMessage(MailItemFolder RemovedFolder, MailAccount Account) : IUIMessage; - public record FolderUpdatedMessage(MailItemFolder UpdatedFolder, MailAccount Account) : IUIMessage; - public record AccountCreatedMessage(MailAccount Account) : IUIMessage; public record AccountRemovedMessage(MailAccount Account) : IUIMessage; public record AccountUpdatedMessage(MailAccount Account) : IUIMessage; @@ -22,4 +19,7 @@ namespace Wino.Core.Requests public record DraftMapped(string LocalDraftCopyId, string RemoteDraftCopyId) : IUIMessage; public record MergedInboxRenamed(Guid MergedInboxId, string NewName) : IUIMessage; + + public record FolderRenamed(IMailItemFolder MailItemFolder) : IUIMessage; + public record FolderSynchronizationEnabled(IMailItemFolder MailItemFolder) : IUIMessage; } diff --git a/Wino.Core/Services/FolderService.cs b/Wino.Core/Services/FolderService.cs index 65808d32..04ffafae 100644 --- a/Wino.Core/Services/FolderService.cs +++ b/Wino.Core/Services/FolderService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using CommunityToolkit.Mvvm.Messaging; using MoreLinq; using Serilog; using SqlKata; @@ -9,10 +10,12 @@ using Wino.Core.Domain; using Wino.Core.Domain.Entities; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Accounts; using Wino.Core.Domain.Models.Folders; using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.Synchronization; using Wino.Core.Extensions; +using Wino.Core.MenuItems; using Wino.Core.Requests; namespace Wino.Core.Services @@ -168,6 +171,155 @@ namespace Wino.Core.Services return accountTree; } + + public Task> GetAccountFoldersForDisplayAsync(IAccountMenuItem accountMenuItem) + { + if (accountMenuItem is IMergedAccountMenuItem mergedAccountFolderMenuItem) + { + return GetMergedAccountFolderMenuItemsAsync(mergedAccountFolderMenuItem); + } + else + { + return GetSingleAccountFolderMenuItemsAsync(accountMenuItem); + } + } + + private async Task GetPreparedFolderMenuItemRecursiveAsync(MailAccount account, MailItemFolder parentFolder, IMenuItem parentMenuItem) + { + // Localize category folder name. + if (parentFolder.SpecialFolderType == SpecialFolderType.Category) parentFolder.FolderName = Translator.CategoriesFolderNameOverride; + + var query = new Query(nameof(MailItemFolder)) + .Where(nameof(MailItemFolder.ParentRemoteFolderId), parentFolder.RemoteFolderId) + .Where(nameof(MailItemFolder.MailAccountId), parentFolder.MailAccountId); + + var preparedFolder = new FolderMenuItem(parentFolder, account, parentMenuItem); + + var childFolders = await Connection.QueryAsync(query.GetRawQuery()).ConfigureAwait(false); + + if (childFolders.Any()) + { + foreach (var subChildFolder in childFolders) + { + var preparedChild = await GetPreparedFolderMenuItemRecursiveAsync(account, subChildFolder, preparedFolder); + + if (preparedChild == null) continue; + + preparedFolder.SubMenuItems.Add(preparedChild); + } + } + + return preparedFolder; + } + + private async Task> GetSingleAccountFolderMenuItemsAsync(IAccountMenuItem accountMenuItem) + { + var accountId = accountMenuItem.EntityId.Value; + var preparedFolderMenuItems = new List(); + + // Get all folders for the account. Excluding hidden folders. + var folders = await GetVisibleFoldersAsync(accountId).ConfigureAwait(false); + + if (!folders.Any()) return new List(); + + var mailAccount = accountMenuItem.HoldingAccounts.First(); + + var listingFolders = folders.OrderBy(a => a.SpecialFolderType); + + var moreFolder = MailItemFolder.CreateMoreFolder(); + var categoryFolder = MailItemFolder.CreateCategoriesFolder(); + + var moreFolderMenuItem = new FolderMenuItem(moreFolder, mailAccount, accountMenuItem); + var categoryFolderMenuItem = new FolderMenuItem(categoryFolder, mailAccount, accountMenuItem); + + foreach (var item in listingFolders) + { + // Category type folders should be skipped. They will be categorized under virtual category folder. + if (GoogleIntegratorExtensions.SubCategoryFolderLabelIds.Contains(item.RemoteFolderId)) continue; + + bool skipEmptyParentRemoteFolders = mailAccount.ProviderType == MailProviderType.Gmail; + + if (skipEmptyParentRemoteFolders && !string.IsNullOrEmpty(item.ParentRemoteFolderId)) continue; + + // Sticky items belong to account menu item directly. Rest goes to More folder. + IMenuItem parentFolderMenuItem = item.IsSticky ? accountMenuItem : (GoogleIntegratorExtensions.SubCategoryFolderLabelIds.Contains(item.FolderName.ToUpper()) ? categoryFolderMenuItem : moreFolderMenuItem); + + var preparedItem = await GetPreparedFolderMenuItemRecursiveAsync(mailAccount, item, parentFolderMenuItem).ConfigureAwait(false); + + // Don't add menu items that are prepared for More folder. They've been included in More virtual folder already. + // We'll add More folder later on at the end of the list. + + if (preparedItem == null) continue; + + if (item.IsSticky) + { + preparedFolderMenuItems.Add(preparedItem); + } + else if (parentFolderMenuItem is FolderMenuItem baseParentFolderMenuItem) + { + baseParentFolderMenuItem.SubMenuItems.Add(preparedItem); + } + } + + // Only add category folder if it's Gmail. + if (mailAccount.ProviderType == MailProviderType.Gmail) preparedFolderMenuItems.Add(categoryFolderMenuItem); + + // Only add More folder if there are any items in it. + if (moreFolderMenuItem.SubMenuItems.Any()) preparedFolderMenuItems.Add(moreFolderMenuItem); + + return preparedFolderMenuItems; + } + + private async Task> GetMergedAccountFolderMenuItemsAsync(IMergedAccountMenuItem mergedAccountFolderMenuItem) + { + var holdingAccounts = mergedAccountFolderMenuItem.HoldingAccounts; + + if (holdingAccounts == null || !holdingAccounts.Any()) return []; + + var preparedFolderMenuItems = new List(); + + // First gather all account folders. + // Prepare single menu items for both of them. + + var allAccountFolders = new List>(); + + foreach (var account in holdingAccounts) + { + var accountFolders = await GetVisibleFoldersAsync(account.Id).ConfigureAwait(false); + + allAccountFolders.Add(accountFolders); + } + + var commonFolders = FindCommonFolders(allAccountFolders); + + // Prepare menu items for common folders. + foreach (var commonFolderType in commonFolders) + { + var folderItems = allAccountFolders.SelectMany(a => a.Where(b => b.SpecialFolderType == commonFolderType)).Cast().ToList(); + var menuItem = new MergedAccountFolderMenuItem(folderItems, null, mergedAccountFolderMenuItem.Parameter); + + preparedFolderMenuItems.Add(menuItem); + } + + return preparedFolderMenuItems; + } + + private HashSet FindCommonFolders(List> lists) + { + var allSpecialTypesExceptOther = Enum.GetValues(typeof(SpecialFolderType)).Cast().Where(a => a != SpecialFolderType.Other).ToList(); + + // Start with all special folder types from the first list + var commonSpecialFolderTypes = new HashSet(allSpecialTypesExceptOther); + + // Intersect with special folder types from all lists + foreach (var list in lists) + { + commonSpecialFolderTypes.IntersectWith(list.Select(f => f.SpecialFolderType)); + } + + return commonSpecialFolderTypes; + } + private async Task GetChildFolderItemsRecursiveAsync(Guid folderId, Guid accountId) { var folder = await Connection.Table().Where(a => a.Id == folderId && a.MailAccountId == accountId).FirstOrDefaultAsync(); @@ -198,49 +350,22 @@ namespace Wino.Core.Services => Connection.Table().Where(a => a.FolderId == folderId).CountAsync(); public Task> GetFoldersAsync(Guid accountId) - => Connection.Table().Where(a => a.MailAccountId == accountId).ToListAsync(); - - public async Task UpdateCustomServerMailListAsync(Guid accountId, List folders) { - var account = await Connection.Table().FirstOrDefaultAsync(a => a.Id == accountId); + var query = new Query(nameof(MailItemFolder)) + .Where(nameof(MailItemFolder.MailAccountId), accountId) + .OrderBy(nameof(MailItemFolder.SpecialFolderType)); - if (account == null) - return; + return Connection.QueryAsync(query.GetRawQuery()); + } - // IMAP servers don't have unique identifier for folders all the time. - // We'll map them with parent-name relation. + public Task> GetVisibleFoldersAsync(Guid accountId) + { + var query = new Query(nameof(MailItemFolder)) + .Where(nameof(MailItemFolder.MailAccountId), accountId) + .Where(nameof(MailItemFolder.IsHidden), false) + .OrderBy(nameof(MailItemFolder.SpecialFolderType)); - var currentFolders = await GetFoldersAsync(accountId); - - // These folders don't exist anymore. Remove them. - var localRemoveFolders = currentFolders.ExceptBy(folders, a => a.RemoteFolderId); - - foreach (var currentFolder in currentFolders) - { - // Check if we have this folder locally. - var remotelyExistFolder = folders.FirstOrDefault(a => a.RemoteFolderId == currentFolder.RemoteFolderId - && a.ParentRemoteFolderId == currentFolder.ParentRemoteFolderId); - - if (remotelyExistFolder == null) - { - // This folder is removed. - // Remove everything for this folder. - - } - } - - foreach (var folder in folders) - { - var currentFolder = await Connection.Table().FirstOrDefaultAsync(a => a.MailAccountId == accountId && a.RemoteFolderId == folder.RemoteFolderId); - - // Nothing is changed, it's still the same folder. - // Just update Id of the folder. - - if (currentFolder != null) - folder.Id = currentFolder.Id; - - await Connection.InsertOrReplaceAsync(folder); - } + return Connection.QueryAsync(query.GetRawQuery()); } public async Task> GetKnownUidsForFolderAsync(Guid folderId) @@ -301,6 +426,8 @@ namespace Wino.Core.Services localFolder.IsSynchronizationEnabled = isSynchronizationEnabled; await UpdateFolderAsync(localFolder).ConfigureAwait(false); + + Messenger.Send(new FolderSynchronizationEnabled(localFolder)); } } @@ -337,11 +464,19 @@ namespace Wino.Core.Services _logger.Debug("Inserting folder {Id} - {FolderName}", folder.Id, folder.FolderName, folder.MailAccountId); await Connection.InsertAsync(folder).ConfigureAwait(false); - - ReportUIChange(new FolderAddedMessage(folder, account)); } else { + // TODO: This is not alright. We should've updated the folder instead of inserting. + // Now we need to match the properties that user might've set locally. + + folder.Id = existingFolder.Id; + folder.IsSticky = existingFolder.IsSticky; + folder.SpecialFolderType = existingFolder.SpecialFolderType; + folder.ShowUnreadCount = existingFolder.ShowUnreadCount; + folder.TextColorHex = existingFolder.TextColorHex; + folder.BackgroundColorHex = existingFolder.BackgroundColorHex; + _logger.Debug("Folder {Id} - {FolderName} already exists. Updating.", folder.Id, folder.FolderName); await UpdateFolderAsync(folder).ConfigureAwait(false); @@ -364,13 +499,9 @@ namespace Wino.Core.Services return; } -#if !DEBUG // Annoying _logger.Debug("Updating folder {FolderName}", folder.Id, folder.FolderName); -#endif await Connection.UpdateAsync(folder).ConfigureAwait(false); - - ReportUIChange(new FolderUpdatedMessage(folder, account)); } private async Task DeleteFolderAsync(MailItemFolder folder) @@ -393,9 +524,10 @@ namespace Wino.Core.Services await Connection.DeleteAsync(folder).ConfigureAwait(false); - // TODO: Delete all mail copies for this folder. + // Delete all existing mails from this folder. + await Connection.ExecuteAsync("DELETE FROM MailCopy WHERE FolderId = ?", folder.Id); - ReportUIChange(new FolderRemovedMessage(folder, account)); + // TODO: Delete MIME messages from the disk. } #endregion @@ -426,8 +558,6 @@ namespace Wino.Core.Services public Task> GetMailFolderPairMetadatasAsync(string mailCopyId) => GetMailFolderPairMetadatasAsync(new List() { mailCopyId }); - public async Task SetSpecialFolderAsync(Guid folderId, SpecialFolderType type) - => await Connection.ExecuteAsync("UPDATE MailItemFolder SET SpecialFolderType = ? WHERE Id = ?", type, folderId); public async Task> GetSynchronizationFoldersAsync(SynchronizationOptions options) { @@ -489,38 +619,6 @@ namespace Wino.Core.Services public Task GetFolderAsync(Guid accountId, string remoteFolderId) => Connection.Table().FirstOrDefaultAsync(a => a.MailAccountId == accountId && a.RemoteFolderId == remoteFolderId); - // v2 - public async Task BulkUpdateFolderStructureAsync(Guid accountId, List allFolders) - { - var existingFolders = await GetFoldersAsync(accountId).ConfigureAwait(false); - - var foldersToInsert = allFolders.ExceptBy(existingFolders, a => a.RemoteFolderId); - var foldersToDelete = existingFolders.ExceptBy(allFolders, a => a.RemoteFolderId); - var foldersToUpdate = allFolders.Except(foldersToInsert).Except(foldersToDelete); - - _logger.Debug("Found {0} folders to insert, {1} folders to update and {2} folders to delete.", - foldersToInsert.Count(), - foldersToUpdate.Count(), - foldersToDelete.Count()); - - foreach (var folder in foldersToInsert) - { - await InsertFolderAsync(folder).ConfigureAwait(false); - } - - foreach (var folder in foldersToUpdate) - { - await UpdateFolderAsync(folder).ConfigureAwait(false); - } - - foreach (var folder in foldersToDelete) - { - await DeleteFolderAsync(folder).ConfigureAwait(false); - } - } - - - public async Task DeleteFolderAsync(Guid accountId, string remoteFolderId) { var folder = await GetFolderAsync(accountId, remoteFolderId); @@ -547,33 +645,6 @@ namespace Wino.Core.Services } } - // Inbox folder is always included for account menu item unread count. - public Task> GetUnreadUpdateFoldersAsync(Guid accountId) - => Connection.Table().Where(a => a.MailAccountId == accountId && (a.ShowUnreadCount || a.SpecialFolderType == SpecialFolderType.Inbox)).ToListAsync(); - - public async Task TestAsync() - { - var account = new MailAccount() - { - Address = "test@test.com", - ProviderType = MailProviderType.Gmail, - Name = "Test Account", - Id = Guid.NewGuid() - }; - - await Connection.InsertAsync(account); - - var pref = new MailAccountPreferences - { - Id = Guid.NewGuid(), - AccountId = account.Id - }; - - await Connection.InsertAsync(pref); - - ReportUIChange(new AccountCreatedMessage(account)); - } - public async Task IsInboxAvailableForAccountAsync(Guid accountId) => (await Connection.Table() .Where(a => a.SpecialFolderType == SpecialFolderType.Inbox && a.MailAccountId == accountId) @@ -581,5 +652,18 @@ namespace Wino.Core.Services public Task UpdateFolderLastSyncDateAsync(Guid folderId) => Connection.ExecuteAsync("UPDATE MailItemFolder SET LastSynchronizedDate = ? WHERE Id = ?", DateTime.UtcNow, folderId); + + public Task> GetUnreadItemCountResultsAsync(IEnumerable accountIds) + { + var query = new Query(nameof(MailCopy)) + .Join(nameof(MailItemFolder), $"{nameof(MailCopy)}.FolderId", $"{nameof(MailItemFolder)}.Id") + .WhereIn($"{nameof(MailItemFolder)}.MailAccountId", accountIds) + .Where($"{nameof(MailCopy)}.IsRead", 0) + .Where($"{nameof(MailItemFolder)}.ShowUnreadCount", 1) + .SelectRaw($"{nameof(MailItemFolder)}.Id as FolderId, {nameof(MailItemFolder)}.SpecialFolderType as SpecialFolderType, count (DISTINCT {nameof(MailCopy)}.Id) as UnreadItemCount, {nameof(MailItemFolder)}.MailAccountId as AccountId") + .GroupBy($"{nameof(MailItemFolder)}.Id"); + + return Connection.QueryAsync(query.GetRawQuery()); + } } } diff --git a/Wino.Core/Services/MailService.cs b/Wino.Core/Services/MailService.cs index b53198b3..d96dcf5c 100644 --- a/Wino.Core/Services/MailService.cs +++ b/Wino.Core/Services/MailService.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.Kiota.Abstractions.Extensions; using MimeKit; -using MimeKit.Text; using MoreLinq; using Serilog; using SqlKata; @@ -111,9 +110,29 @@ namespace Wino.Core.Services return copy; } - public Task> GetMailIdsByFolderIdAsync(Guid folderId) - => Connection.QueryScalarsAsync("SELECT Id FROM MailCopy WHERE FolderId = ?", folderId); + public async Task> GetMailsByFolderIdAsync(Guid folderId) + { + var mails = await Connection.QueryAsync("SELECT * FROM MailCopy WHERE FolderId = ?", folderId); + foreach (var mail in mails) + { + await LoadAssignedPropertiesAsync(mail).ConfigureAwait(false); + } + + return mails; + } + + public async Task> GetUnreadMailsByFolderIdAsync(Guid folderId) + { + var unreadMails = await Connection.QueryAsync("SELECT * FROM MailCopy WHERE FolderId = ? AND IsRead = 0", folderId); + + foreach (var mail in unreadMails) + { + await LoadAssignedPropertiesAsync(mail).ConfigureAwait(false); + } + + return unreadMails; + } private string BuildMailFetchQuery(MailListInitializationOptions options) { diff --git a/Wino.Core/Services/WinoRequestDelegator.cs b/Wino.Core/Services/WinoRequestDelegator.cs index 8aba36b9..af1bd00e 100644 --- a/Wino.Core/Services/WinoRequestDelegator.cs +++ b/Wino.Core/Services/WinoRequestDelegator.cs @@ -10,7 +10,6 @@ using Wino.Core.Domain.Exceptions; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Folders; using Wino.Core.Domain.Models.MailItem; -using Wino.Core.Domain.Models.Requests; using Wino.Core.Domain.Models.Synchronization; using Wino.Core.Messages.Synchronization; using Wino.Core.Requests; @@ -85,13 +84,17 @@ namespace Wino.Core.Services } } - public async Task ExecuteAsync(FolderOperation operation, IMailItemFolder folderStructure) + public async Task ExecuteAsync(FolderOperationPreperationRequest folderRequest) { - IRequest request = null; + if (folderRequest == null || folderRequest.Folder == null) return; + + IRequestBase request = null; + + var accountId = folderRequest.Folder.MailAccountId; try { - request = await _winoRequestProcessor.PrepareFolderRequestAsync(operation, folderStructure); + request = await _winoRequestProcessor.PrepareFolderRequestAsync(folderRequest); } catch (NotImplementedException) { @@ -102,7 +105,10 @@ namespace Wino.Core.Services Log.Error(ex, "Folder operation execution failed."); } - // _synchronizationWorker.Queue(request); + if (request == null) return; + + QueueRequest(request, accountId); + QueueSynchronization(accountId); } public Task ExecuteAsync(DraftPreperationRequest draftPreperationRequest) @@ -125,7 +131,7 @@ namespace Wino.Core.Services return Task.CompletedTask; } - private void QueueRequest(IRequest request, Guid accountId) + private void QueueRequest(IRequestBase request, Guid accountId) { var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(accountId); diff --git a/Wino.Core/Services/WinoRequestProcessor.cs b/Wino.Core/Services/WinoRequestProcessor.cs index 768fdaa5..ffcf3f01 100644 --- a/Wino.Core/Services/WinoRequestProcessor.cs +++ b/Wino.Core/Services/WinoRequestProcessor.cs @@ -215,45 +215,45 @@ namespace Wino.Core.Services return null; } - public async Task PrepareFolderRequestAsync(FolderOperation operation, IMailItemFolder mailItemFolder) + public async Task PrepareFolderRequestAsync(FolderOperationPreperationRequest request) { - if (mailItemFolder == null) return default; + if (request == null || request.Folder == null) return default; - var accountId = mailItemFolder.MailAccountId; + IRequestBase change = null; - IRequest change = null; + var folder = request.Folder; + var operation = request.Action; - switch (operation) + switch (request.Action) { case FolderOperation.Pin: case FolderOperation.Unpin: - await _folderService.ChangeStickyStatusAsync(mailItemFolder.Id, operation == FolderOperation.Pin); + await _folderService.ChangeStickyStatusAsync(folder.Id, operation == FolderOperation.Pin); break; - //case FolderOperation.MarkAllAsRead: - // // Get all mails in the folder. - // var mailItems = await _folderService.GetAllUnreadItemsByFolderIdAsync(accountId, folderStructure.RemoteFolderId).ConfigureAwait(false); + case FolderOperation.Rename: + var newFolderName = await _dialogService.ShowTextInputDialogAsync(folder.FolderName, Translator.DialogMessage_RenameFolderTitle, Translator.DialogMessage_RenameFolderMessage, Translator.FolderOperation_Rename); - // if (mailItems.Any()) - // change = new FolderMarkAsReadRequest(accountId, mailItems.Select(a => a.Id).Distinct(), folderStructure.RemoteFolderId, folderStructure.FolderId); + if (!string.IsNullOrEmpty(newFolderName)) + { + change = new RenameFolderRequest(folder, folder.FolderName, newFolderName); + } - // break; - //case FolderOperation.Empty: - // // Get all mails in the folder. + break; + case FolderOperation.Empty: + var mailsToDelete = await _mailService.GetMailsByFolderIdAsync(folder.Id).ConfigureAwait(false); - // var mailsToDelete = await _folderService.GetMailByFolderIdAsync(folderStructure.FolderId).ConfigureAwait(false); + change = new EmptyFolderRequest(folder, mailsToDelete); - // if (mailsToDelete.Any()) - // change = new FolderEmptyRequest(accountId, mailsToDelete.Select(a => a.Id).Distinct(), folderStructure.RemoteFolderId, folderStructure.FolderId); + break; + case FolderOperation.MarkAllAsRead: - // break; - //case FolderOperation.Rename: - // var newFolderName = await _dialogService.ShowRenameFolderDialogAsync(folderStructure.FolderName); + var unreadItems = await _mailService.GetUnreadMailsByFolderIdAsync(folder.Id).ConfigureAwait(false); - // if (!string.IsNullOrEmpty(newFolderName)) - // change = new RenameFolderRequest(accountId, folderStructure.RemoteFolderId, folderStructure.FolderId, newFolderName, folderStructure.FolderName); + if (unreadItems.Any()) + change = new MarkFolderAsReadRequest(folder, unreadItems); - // break; + break; //case FolderOperation.Delete: // var isConfirmed = await _dialogService.ShowConfirmationDialogAsync($"'{folderStructure.FolderName}' is going to be deleted. Do you want to continue?", "Are you sure?", "Yes delete."); diff --git a/Wino.Core/Synchronizers/BaseSynchronizer.cs b/Wino.Core/Synchronizers/BaseSynchronizer.cs index c1d700a4..246c432f 100644 --- a/Wino.Core/Synchronizers/BaseSynchronizer.cs +++ b/Wino.Core/Synchronizers/BaseSynchronizer.cs @@ -149,11 +149,10 @@ namespace Wino.Core.Synchronizers await synchronizationSemaphore.WaitAsync(activeSynchronizationCancellationToken); // Let servers to finish their job. Sometimes the servers doesn't respond immediately. - // TODO: Outlook sends back the deleted Draft. Might be a bug in the graph API or in Wino. - var hasSendDraftRequest = batches.Any(a => a is BatchSendDraftRequestRequest); + bool shouldDelayExecution = batches.Any(a => a.DelayExecution); - if (hasSendDraftRequest && DelaySendOperationSynchronization()) + if (shouldDelayExecution) { await Task.Delay(2000); } @@ -227,8 +226,13 @@ namespace Wino.Core.Synchronizers changeRequestQueue.TryTake(out _); } } - else + else if (changeRequestQueue.TryTake(out request)) + { + // This is a folder operation. + // There is no need to batch them since Users can't do folder ops in bulk. + batchList.Add(request); + } } } @@ -272,6 +276,15 @@ namespace Wino.Core.Synchronizers case MailSynchronizerOperation.CreateDraft: yield return CreateDraft((BatchCreateDraftRequest)item); break; + case MailSynchronizerOperation.RenameFolder: + yield return RenameFolder((RenameFolderRequest)item); + break; + case MailSynchronizerOperation.EmptyFolder: + yield return EmptyFolder((EmptyFolderRequest)item); + break; + case MailSynchronizerOperation.MarkFolderRead: + yield return MarkFolderAsRead((MarkFolderAsReadRequest)item); + break; case MailSynchronizerOperation.Archive: yield return Archive((BatchArchiveRequest)item); break; @@ -287,12 +300,9 @@ namespace Wino.Core.Synchronizers /// /// Batch requests to run in synchronization. /// New synchronization options with minimal HTTP effort. - private SynchronizationOptions GetSynchronizationOptionsAfterRequestExecution(IEnumerable batches) + private SynchronizationOptions GetSynchronizationOptionsAfterRequestExecution(IEnumerable requests) { - // TODO: Check folders only. - var batchItems = batches.Where(a => a is IBatchChangeRequest).Cast(); - - var requests = batchItems.SelectMany(a => a.Items); + bool isAllCustomSynchronizationRequests = requests.All(a => a is ICustomFolderSynchronizationRequest); var options = new SynchronizationOptions() { @@ -300,9 +310,7 @@ namespace Wino.Core.Synchronizers Type = SynchronizationType.FoldersOnly }; - bool isCustomSynchronization = requests.All(a => a is ICustomFolderSynchronizationRequest); - - if (isCustomSynchronization) + if (isAllCustomSynchronizationRequests) { // Gather FolderIds to synchronize. @@ -327,6 +335,9 @@ namespace Wino.Core.Synchronizers public virtual IEnumerable> MoveToFocused(BatchMoveToFocusedRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); public virtual IEnumerable> CreateDraft(BatchCreateDraftRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); public virtual IEnumerable> SendDraft(BatchSendDraftRequestRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual IEnumerable> RenameFolder(RenameFolderRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual IEnumerable> EmptyFolder(EmptyFolderRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual IEnumerable> MarkFolderAsRead(MarkFolderAsReadRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); public virtual IEnumerable> Archive(BatchArchiveRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); /// diff --git a/Wino.Core/Synchronizers/GmailSynchronizer.cs b/Wino.Core/Synchronizers/GmailSynchronizer.cs index 357edcf1..c100366d 100644 --- a/Wino.Core/Synchronizers/GmailSynchronizer.cs +++ b/Wino.Core/Synchronizers/GmailSynchronizer.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; -using System.Text; using System.Threading; using System.Threading.Tasks; using Google.Apis.Gmail.v1; @@ -245,111 +244,97 @@ namespace Wino.Core.Synchronizers private async Task SynchronizeFoldersAsync(CancellationToken cancellationToken = default) { - var folderRequest = _gmailService.Users.Labels.List("me"); - - var labelsResponse = await folderRequest.ExecuteAsync(cancellationToken).ConfigureAwait(false); - - if (labelsResponse.Labels == null) + try { - _logger.Warning("No folders found for {Name}", Account.Name); - return; - } + var localFolders = await _gmailChangeProcessor.GetLocalFoldersAsync(Account.Id).ConfigureAwait(false); + var folderRequest = _gmailService.Users.Labels.List("me"); - _logger.Debug($"Gmail folders found: {string.Join(",", labelsResponse.Labels.Select(a => a.Name))}"); + var labelsResponse = await folderRequest.ExecuteAsync(cancellationToken).ConfigureAwait(false); - // Gmail has special Categories parent folder. - var categoriesFolder = new MailItemFolder() - { - SpecialFolderType = SpecialFolderType.Category, - MailAccountId = Account.Id, - FolderName = "Categories", - IsSynchronizationEnabled = false, - IsSticky = true, - Id = Guid.NewGuid(), - RemoteFolderId = "Categories" - }; - - var initializedFolders = new List() { categoriesFolder }; - - foreach (var label in labelsResponse.Labels) - { - var localFolder = label.GetLocalFolder(Account.Id); - - localFolder.MailAccountId = Account.Id; - - initializedFolders.Add(localFolder); - } - - // 1. Create parent-child relations but first order by name desc to be able to not mess up FolderNames in memory. - // 2. Mark special folder types that belong to categories folder. - - var ordered = initializedFolders.OrderByDescending(a => a.FolderName); - - // TODO: This can be refactored better. - foreach (var label in ordered) - { - // Gmail categorizes sub-labels by '/' For example: - // ParentTest/SubTest in the name of label means - // SubTest is a sub-label of ParentTest label. - - if (label.SpecialFolderType == SpecialFolderType.Promotions || - label.SpecialFolderType == SpecialFolderType.Updates || - label.SpecialFolderType == SpecialFolderType.Social || - label.SpecialFolderType == SpecialFolderType.Forums || - label.SpecialFolderType == SpecialFolderType.Personal) + if (labelsResponse.Labels == null) { - label.ParentRemoteFolderId = categoriesFolder.RemoteFolderId; - - // These folders can not be a sub folder. - continue; + _logger.Warning("No folders found for {Name}", Account.Name); + return; } - var isSubFolder = label.FolderName.Contains("/"); + List insertedFolders = new(); + List updatedFolders = new(); + List deletedFolders = new(); - if (isSubFolder) + // 1. Handle deleted labels. + + foreach (var localFolder in localFolders) { - var splittedFolderName = label.FolderName.Split('/'); - var partCount = splittedFolderName.Length; + // Category folder is virtual folder for Wino. Skip it. + if (localFolder.SpecialFolderType == SpecialFolderType.Category) continue; - if (partCount > 1) + var remoteFolder = labelsResponse.Labels.FirstOrDefault(a => a.Id == localFolder.RemoteFolderId); + + if (remoteFolder == null) { - // Only make the last part connection since other relations will build up in the loop. + // Local folder doesn't exists remotely. Delete local copy. + await _gmailChangeProcessor.DeleteFolderAsync(Account.Id, localFolder.RemoteFolderId).ConfigureAwait(false); - var realChildFolderName = splittedFolderName[splittedFolderName.Length - 1]; + deletedFolders.Add(localFolder); + } + } - var childFolder = initializedFolders.Find(a => a.FolderName.EndsWith(realChildFolderName)); + // Delete the deleted folders from local list. + deletedFolders.ForEach(a => localFolders.Remove(a)); - string GetParentFolderName(string[] parts) + // 2. Handle update/insert based on remote folders. + foreach (var remoteFolder in labelsResponse.Labels) + { + var existingLocalFolder = localFolders.FirstOrDefault(a => a.RemoteFolderId == remoteFolder.Id); + + if (existingLocalFolder == null) + { + // Insert new folder. + var localFolder = remoteFolder.GetLocalFolder(labelsResponse, Account.Id); + + insertedFolders.Add(localFolder); + } + else + { + // Update existing folder. Right now we only update the name. + + // TODO: Moving folders around different parents. This is not supported right now. + // We will need more comphrensive folder update mechanism to support this. + + if (ShouldUpdateFolder(remoteFolder, existingLocalFolder)) { - var builder = new StringBuilder(); - - for (int i = 0; i < parts.Length - 1; i++) - { - builder.Append(parts[i]); - - if (i != parts.Length - 2) - builder.Append("/"); - } - - return builder.ToString(); + existingLocalFolder.FolderName = remoteFolder.Name; + updatedFolders.Add(existingLocalFolder); } - - var parentFolderName = GetParentFolderName(splittedFolderName); - - var parentFolder = initializedFolders.Find(a => a.FolderName == parentFolderName); - - if (childFolder != null && parentFolder != null) + else { - childFolder.FolderName = realChildFolderName; - childFolder.ParentRemoteFolderId = parentFolder.RemoteFolderId; + // Remove it from the local folder list to skip additional folder updates. + localFolders.Remove(existingLocalFolder); } } } - } - await _gmailChangeProcessor.UpdateFolderStructureAsync(Account.Id, initializedFolders).ConfigureAwait(false); + // 3.Process changes in order-> Insert, Update. Deleted ones are already processed. + + foreach (var folder in insertedFolders) + { + await _gmailChangeProcessor.InsertFolderAsync(folder).ConfigureAwait(false); + } + + foreach (var folder in updatedFolders) + { + await _gmailChangeProcessor.UpdateFolderAsync(folder).ConfigureAwait(false); + } + } + catch (Exception) + { + throw; + } } + private bool ShouldUpdateFolder(Label remoteFolder, MailItemFolder existingLocalFolder) + => existingLocalFolder.FolderName.Equals(GoogleIntegratorExtensions.GetFolderName(remoteFolder), StringComparison.OrdinalIgnoreCase) == false; + /// /// Returns a single get request to retrieve the raw message with the given id /// @@ -706,6 +691,34 @@ namespace Wino.Core.Synchronizers await _gmailChangeProcessor.SaveMimeFileAsync(mailItem.FileId, mimeMessage, Account.Id).ConfigureAwait(false); } + public override IEnumerable> RenameFolder(RenameFolderRequest request) + { + return CreateHttpBundleWithResponse