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.
This commit is contained in:
Burak Kaan Köse
2024-07-09 01:05:16 +02:00
committed by GitHub
parent ac01006398
commit 536fbb23a1
67 changed files with 1396 additions and 1585 deletions

View File

@@ -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;
}
}

View File

@@ -11,8 +11,10 @@
ChangeFlag,
AlwaysMoveTo,
MoveToFocused,
Archive,
RenameFolder,
Archive
EmptyFolder,
MarkFolderRead,
}
// UI requests

View File

@@ -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<MailAccount> HoldingAccounts { get; }
void UpdateAccount(MailAccount account);
}
public interface IMergedAccountMenuItem : IAccountMenuItem
{
int MergedAccountCount { get; }
MergedInbox Parameter { get; }
}
}

View File

@@ -29,7 +29,7 @@ namespace Wino.Core.Domain.Interfaces
Task<IMailItemFolder> ShowMoveMailFolderDialogAsync(List<IMailItemFolder> availableFolders);
Task<AccountCreationDialogResult> ShowNewAccountMailProviderDialogAsync(List<IProviderDetail> availableProviders);
IAccountCreationDialog GetAccountCreationDialog(MailProviderType type);
Task<string> ShowTextInputDialogAsync(string currentInput, string dialogTitle, string dialogDescription);
Task<string> ShowTextInputDialogAsync(string currentInput, string dialogTitle, string dialogDescription, string primaryButtonText);
Task<MailAccount> ShowEditAccountDialogAsync(MailAccount account);
Task<MailAccount> ShowAccountPickerDialogAsync(List<MailAccount> availableAccounts);

View File

@@ -20,6 +20,7 @@ namespace Wino.Core.Domain.Interfaces
int UnreadItemCount { get; set; }
SpecialFolderType SpecialFolderType { get; }
IEnumerable<IMailItemFolder> HandlingFolders { get; }
IEnumerable<IMenuItem> SubMenuItems { get; }
bool IsMoveTarget { get; }
bool IsSticky { get; }
bool IsSystemFolder { get; }

View File

@@ -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<MailItemFolder> GetFolderAsync(Guid folderId);
Task<MailItemFolder> GetFolderAsync(Guid accountId, string remoteFolderId);
Task<List<MailItemFolder>> GetFoldersAsync(Guid accountId);
Task<List<MailItemFolder>> GetUnreadUpdateFoldersAsync(Guid accountId);
Task SetSpecialFolderAsync(Guid folderId, SpecialFolderType type);
Task<MailItemFolder> GetSpecialFolderByAccountIdAsync(Guid accountId, SpecialFolderType type);
Task<int> GetCurrentItemCountForFolder(Guid folderId);
Task<int> GetFolderNotificationBadgeAsync(Guid folderId);
Task ChangeStickyStatusAsync(Guid folderId, bool isSticky);
Task UpdateCustomServerMailListAsync(Guid accountId, List<MailItemFolder> folders);
Task<MailAccount> UpdateSystemFolderConfigurationAsync(Guid accountId, SystemFolderConfiguration configuration);
Task ChangeFolderSynchronizationStateAsync(Guid folderId, bool isSynchronizationEnabled);
@@ -39,16 +37,6 @@ namespace Wino.Core.Domain.Interfaces
/// </summary>
Task<List<MailFolderPairMetadata>> GetMailFolderPairMetadatasAsync(string mailCopyId);
// v2
/// <summary>
/// Performs bulk update for the given folders.
/// Used in Gmail.
/// </summary>
/// <param name="accountId">Account that folders belong to.</param>
/// <param name="allFolders">Folders to update.</param>
Task BulkUpdateFolderStructureAsync(Guid accountId, List<MailItemFolder> allFolders);
/// <summary>
/// Deletes the folder for the given account by remote folder id.
/// </summary>
@@ -84,12 +72,23 @@ namespace Wino.Core.Domain.Interfaces
/// <param name="folderId">Folder to update.</param>
Task UpdateFolderLastSyncDateAsync(Guid folderId);
Task TestAsync();
/// <summary>
/// Updates the given folder.
/// </summary>
/// <param name="folder">Folder to update.</param>
Task UpdateFolderAsync(MailItemFolder folder);
/// <summary>
/// Returns the active folder menu items for the given account for UI.
/// </summary>
/// <param name="accountMenuItem">Account to get folder menu items for.</param>
Task<IEnumerable<IMenuItem>> GetAccountFoldersForDisplayAsync(IAccountMenuItem accountMenuItem);
/// <summary>
/// Returns a list of unread item counts for the given account ids.
/// Every folder that is marked as show unread badge is included.
/// </summary>
/// <param name="accountIds">Account ids to get unread folder counts for.</param>
Task<List<UnreadItemCountResult>> GetUnreadItemCountResultsAsync(IEnumerable<Guid> accountIds);
}
}

View File

@@ -14,10 +14,6 @@ namespace Wino.Core.Domain.Interfaces
Task<MailCopy> CreateDraftAsync(MailAccount composerAccount, MimeMessage generatedReplyMime, MimeMessage replyingMimeMessage = null, IMailItem replyingMailItem = null);
Task<List<IMailItem>> FetchMailsAsync(MailListInitializationOptions options);
Task<List<string>> GetMailIdsByFolderIdAsync(Guid folderId);
// v2
/// <summary>
/// Deletes all mail copies for all folders.
/// </summary>
@@ -84,5 +80,17 @@ namespace Wino.Core.Domain.Interfaces
/// </summary>
/// <param name="mailCopyId">Native mail id of the message.</param>
Task<bool> IsMailExistsAsync(string mailCopyId);
/// <summary>
/// Returns all mails for given folder id.
/// </summary>
/// <param name="folderId">Folder id to get mails for</param>
Task<List<MailCopy>> GetMailsByFolderIdAsync(Guid folderId);
/// <summary>
/// Returns all unread mails for given folder id.
/// </summary>
/// <param name="folderId">Folder id to get unread mails for.</param>
Task<List<MailCopy>> GetUnreadMailsByFolderIdAsync(Guid folderId);
}
}

View File

@@ -40,6 +40,15 @@ namespace Wino.Core.Domain.Interfaces
/// Reverts the UI changes applied by <see cref="ApplyUIChanges"/> if the request fails.
/// </summary>
void RevertUIChanges();
/// <summary>
/// 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.
/// </summary>
bool DelayExecution { get; }
}
public interface IRequest : IRequestBase

View File

@@ -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
/// <summary>
/// Prepares requires IRequest collection for folder actions and executes them via proper synchronizers.
/// </summary>
/// <param name="operation">Folder operation to execute.</param>
/// <param name="folderStructure">Target folder</param>
Task ExecuteAsync(FolderOperation operation, IMailItemFolder folderStructure);
/// <param name="folderOperationPreperationRequest">Folder prep request.</param>
Task ExecuteAsync(FolderOperationPreperationRequest folderOperationPreperationRequest);
}
}

View File

@@ -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<IRequest> PrepareFolderRequestAsync(FolderOperation operation, IMailItemFolder mailItemFolder);
/// <summary>
/// Prepares proper folder action requests for synchronizers to execute.
/// </summary>
/// <param name="request"></param>
/// <returns>Base request that synchronizer can execute.</returns>
Task<IRequestBase> PrepareFolderRequestAsync(FolderOperationPreperationRequest request);
/// <summary>
/// Prepares proper Wino requests for synchronizers to execute categorized by AccountId and FolderId.
@@ -17,6 +20,7 @@ namespace Wino.Core.Domain.Interfaces
/// <param name="operation">User action</param>
/// <param name="mailCopyIds">Selected mails.</param>
/// <exception cref="UnavailableSpecialFolderException">When required folder target is not available for account.</exception>
/// <returns>Base request that synchronizer can execute.</returns>
Task<List<IRequest>> PrepareRequestsAsync(MailOperationPreperationRequest request);
}
}

View File

@@ -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; }
}
}

View File

@@ -0,0 +1,12 @@
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.Folders
{
/// <summary>
/// Encapsulates a request to prepare a folder operation like Rename, Delete, etc.
/// </summary>
/// <param name="Action">Folder operation.</param>
/// <param name="Folder">Target folder.</param>
public record FolderOperationPreperationRequest(FolderOperation Action, MailItemFolder Folder) { }
}

View File

@@ -9,61 +9,31 @@ namespace Wino.Core.Domain.Models.MailItem
/// <summary>
/// Encapsulates the options for preparing requests to execute mail operations for mail items like Move, Delete, MarkAsRead, etc.
/// </summary>
public class MailOperationPreperationRequest
/// <param name="Action"> Action to execute. </param>
/// <param name="MailItems"> Mail copies execute the action on. </param>
/// <param name="ToggleExecution"> 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. </param>
/// <param name="IgnoreHardDeleteProtection"> Whether hard delete protection should be ignored.
/// Discard draft requests for example should ignore hard delete protection. </param>
/// <param name="MoveTargetFolder"> Moving folder for the Move operation.
/// If null and the action is Move, the user will be prompted to select a folder. </param>
public record MailOperationPreperationRequest(MailOperation Action, IEnumerable<MailCopy> MailItems, bool ToggleExecution, bool IgnoreHardDeleteProtection, IMailItemFolder MoveTargetFolder)
{
public MailOperationPreperationRequest(MailOperation action,
IEnumerable<MailCopy> 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<MailCopy>() { singleMailItem }, toggleExecution, ignoreHardDeleteProtection, moveTargetFolder)
{
Action = action;
MailItems = new List<MailCopy>() { singleMailItem };
ToggleExecution = toggleExecution;
MoveTargetFolder = moveTargetFolder;
IgnoreHardDeleteProtection = ignoreHardDeleteProtection;
}
/// <summary>
/// Action to execute.
/// </summary>
public MailOperation Action { get; set; }
/// <summary>
/// Mail copies execute the action on.
/// </summary>
public IEnumerable<MailCopy> MailItems { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool ToggleExecution { get; set; }
/// <summary>
/// Whether hard delete protection should be ignored.
/// Discard draft requests for example should ignore hard delete protection.
/// </summary>
public bool IgnoreHardDeleteProtection { get; set; }
/// <summary>
/// Moving folder for the Move operation.
/// If null and the action is Move, the user will be prompted to select a folder.
/// </summary>
public IMailItemFolder MoveTargetFolder { get; }
}
}

View File

@@ -3,6 +3,6 @@
public enum NavigationTransitionType
{
None, // Supress
DrillIn,
DrillIn
}
}

View File

@@ -11,17 +11,23 @@ namespace Wino.Core.Domain.Models.Requests
public abstract IBatchChangeRequest CreateBatch(IEnumerable<IRequest> 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<IRequest> Items, MailSynchronizerOperation Operation) : IBatchChangeRequest
{
public abstract void ApplyUIChanges();
public abstract void RevertUIChanges();
public virtual bool DelayExecution => false;
}
}

View File

@@ -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.",

File diff suppressed because it is too large Load Diff