Managing account aliases and profile synchronization for outlook and gmail.

This commit is contained in:
Burak Kaan Köse
2024-08-17 03:43:37 +02:00
parent f1154058ba
commit abff850427
46 changed files with 949 additions and 272 deletions

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using SQLite;
using Wino.Core.Domain.Enums;
@@ -48,7 +47,7 @@ namespace Wino.Core.Domain.Entities
/// <summary>
/// Base64 encoded profile picture of the account.
/// </summary>
public string ProfilePictureBase64 { get; set; }
public string Base64ProfilePictureData { get; set; }
/// <summary>
/// Gets or sets the listing order of the account in the accounts list.
@@ -84,8 +83,8 @@ namespace Wino.Core.Domain.Entities
/// It's only synchronized for Gmail right now.
/// Other provider types are manually added by users and not verified.
/// </summary>
[Ignore]
public List<MailAccountAlias> Aliases { get; set; }
//[Ignore]
//public List<MailAccountAlias> Aliases { get; set; }
/// <summary>
/// Account preferences.

View File

@@ -32,6 +32,13 @@ namespace Wino.Core.Domain.Entities
/// </summary>
public bool IsPrimary { get; set; }
/// <summary>
/// Whether this alias is the root alias for the account.
/// Root alias means the first alias that was created for the account.
/// It can't be deleted or changed.
/// </summary>
public bool IsRootAlias { get; set; }
/// <summary>
/// Whether the alias is verified by the server.
/// Non-verified aliases will show an info tip to users during sending.
@@ -40,6 +47,11 @@ namespace Wino.Core.Domain.Entities
/// </summary>
public bool IsVerified { get; set; }
/// <summary>
/// Root aliases can't be deleted.
/// </summary>
public bool CanDelete => !IsRootAlias;
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
@@ -51,17 +63,20 @@ namespace Wino.Core.Domain.Entities
AliasAddress == other.AliasAddress &&
ReplyToAddress == other.ReplyToAddress &&
IsPrimary == other.IsPrimary &&
IsVerified == other.IsVerified;
IsVerified == other.IsVerified &&
IsRootAlias == other.IsRootAlias;
}
public override int GetHashCode()
{
int hashCode = -753829106;
int hashCode = 59052167;
hashCode = hashCode * -1521134295 + AccountId.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(AliasAddress);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(ReplyToAddress);
hashCode = hashCode * -1521134295 + IsPrimary.GetHashCode();
hashCode = hashCode * -1521134295 + IsRootAlias.GetHashCode();
hashCode = hashCode * -1521134295 + IsVerified.GetHashCode();
hashCode = hashCode * -1521134295 + CanDelete.GetHashCode();
return hashCode;
}
}

View File

@@ -9,6 +9,7 @@
ManuelSetupWaiting,
TestingConnection,
AutoDiscoverySetup,
AutoDiscoveryInProgress
AutoDiscoveryInProgress,
FetchingProfileInformation
}
}

View File

@@ -7,5 +7,6 @@
Inbox, // Only Inbox
Custom, // Only sync folders that are specified in the options.
Full, // Synchronize everything
UpdateProfile, // Only update profile information
}
}

View File

@@ -23,5 +23,6 @@
LanguageTimePage,
AppPreferencesPage,
SettingOptionsPage,
AliasManagementPage
}
}

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Models.Accounts;
namespace Wino.Core.Domain.Interfaces
{
@@ -101,12 +102,42 @@ namespace Wino.Core.Domain.Interfaces
/// <param name="accountIdOrderPair">AccountId-OrderNumber pair for all accounts.</param>
Task UpdateAccountOrdersAsync(Dictionary<Guid, int> accountIdOrderPair);
/// <summary>
/// Returns the account aliases.
/// </summary>
/// <param name="accountId">Account id.</param>
/// <returns>A list of MailAccountAlias that has e-mail aliases.</returns>
Task<List<MailAccountAlias>> GetAccountAliasesAsync(Guid accountId);
/// <summary>
/// Updated account's aliases.
/// </summary>
/// <param name="accountId">Account id to update aliases for.</param>
/// <param name="aliases">Full list of updated aliases.</param>
/// <returns></returns>
Task UpdateAccountAliases(Guid accountId, List<MailAccountAlias> aliases);
Task UpdateAccountAliasesAsync(Guid accountId, List<MailAccountAlias> aliases);
/// <summary>
/// Delete account alias.
/// </summary>
/// <param name="aliasId">Alias to remove.</param>
Task DeleteAccountAliasAsync(Guid aliasId);
/// <summary>
/// Updated profile information of the account.
/// </summary>
/// <param name="accountId">Account id to update info for.</param>
/// <param name="profileInformation">Info data.</param>
/// <returns></returns>
Task UpdateProfileInformationAsync(Guid accountId, ProfileInformation profileInformation);
/// <summary>
/// Creates a root + primary alias for the account.
/// This is only called when the account is created.
/// </summary>
/// <param name="accountId">Account id.</param>
/// <param name="address">Address to create root primary alias from.</param>
Task CreateRootAliasAsync(Guid accountId, string address);
}
}

View File

@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using MailKit;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Accounts;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Synchronization;
@@ -43,6 +44,13 @@ namespace Wino.Core.Domain.Interfaces
/// <returns>Result summary of synchronization.</returns>
Task<SynchronizationResult> SynchronizeAsync(SynchronizationOptions options, CancellationToken cancellationToken = default);
/// <summary>
/// Synchronizes profile information with the server.
/// Sender name and
/// </summary>
/// <returns></returns>
Task<ProfileInformation> SynchronizeProfileInformationAsync();
/// <summary>
/// Downloads a single MIME message from the server and saves it to disk.
/// </summary>

View File

@@ -0,0 +1,9 @@
using Wino.Core.Domain.Entities;
namespace Wino.Core.Domain.Interfaces
{
public interface ICreateAccountAliasDialog
{
public MailAccountAlias CreatedAccountAlias { get; set; }
}
}

View File

@@ -53,5 +53,6 @@ namespace Wino.Core.Domain.Interfaces
/// </summary>
/// <returns>Signature information. Null if canceled.</returns>
Task<AccountSignature> ShowSignatureEditorDialog(AccountSignature signatureModel = null);
Task<ICreateAccountAliasDialog> ShowCreateAccountAliasDialogAsync();
}
}

View File

@@ -2,5 +2,5 @@
namespace Wino.Core.Domain.Models.Accounts
{
public record AccountCreationDialogResult(MailProviderType ProviderType, string AccountName, string SenderName, string AccountColorHex = "");
public record AccountCreationDialogResult(MailProviderType ProviderType, string AccountName, string AccountColorHex = "");
}

View File

@@ -0,0 +1,9 @@
namespace Wino.Core.Domain.Models.Accounts
{
/// <summary>
/// Encapsulates the profile information of an account.
/// </summary>
/// <param name="SenderName">Display sender name for the account.</param>
/// <param name="Base64ProfilePictureData">Base 64 encoded profile picture data of the account. Thumbnail size.</param>
public record ProfileInformation(string SenderName, string Base64ProfilePictureData);
}

View File

@@ -14,7 +14,6 @@ namespace Wino.Core.Domain.Models.Accounts
public string ProviderImage => $"ms-appx:///Assets/Providers/{Type}.png";
public bool IsSupported => Type == MailProviderType.Outlook || Type == MailProviderType.Gmail || Type == MailProviderType.IMAP4;
public bool RequireSenderNameOnCreationDialog => Type != MailProviderType.IMAP4;
public ProviderDetail(MailProviderType type)
{

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Accounts;
using Wino.Core.Domain.Models.MailItem;
namespace Wino.Core.Domain.Models.Synchronization
@@ -15,14 +16,18 @@ namespace Wino.Core.Domain.Models.Synchronization
/// It's ignored in serialization. Client should not react to this.
/// </summary>
[JsonIgnore]
public IEnumerable<IMailItem> DownloadedMessages { get; set; } = new List<IMailItem>();
public IEnumerable<IMailItem> DownloadedMessages { get; set; } = [];
public ProfileInformation ProfileInformation { get; set; }
public SynchronizationCompletedState CompletedState { get; set; }
public static SynchronizationResult Empty => new() { CompletedState = SynchronizationCompletedState.Success };
public static SynchronizationResult Completed(IEnumerable<IMailItem> downloadedMessages)
=> new() { DownloadedMessages = downloadedMessages, CompletedState = SynchronizationCompletedState.Success };
public static SynchronizationResult Completed(IEnumerable<IMailItem> downloadedMessages, ProfileInformation profileInformation = null)
=> new() { DownloadedMessages = downloadedMessages, ProfileInformation = profileInformation, CompletedState = SynchronizationCompletedState.Success };
public static SynchronizationResult Canceled => new() { CompletedState = SynchronizationCompletedState.Canceled };
public static SynchronizationResult Failed => new() { CompletedState = SynchronizationCompletedState.Failed };
}
}

View File

@@ -3,6 +3,7 @@
"AccountCreationDialog_Initializing": "initializing",
"AccountCreationDialog_PreparingFolders": "We are getting folder information at the moment.",
"AccountCreationDialog_SigninIn": "Account information is being saved.",
"AccountCreationDialog_FetchingProfileInformation": "Fetching profile details.",
"AccountEditDialog_Message": "Account Name",
"AccountEditDialog_Title": "Edit Account",
"AccountPickerDialog_Title": "Pick an account",
@@ -64,6 +65,14 @@
"CustomThemeBuilder_WallpaperTitle": "Set custom wallpaper",
"DialogMessage_AccountLimitMessage": "You have reached the account creation limit.\nWould you like to purchase 'Unlimited Account' add-on to continue?",
"DialogMessage_AccountLimitTitle": "Account Limit Reached",
"DialogMessage_AliasExistsTitle": "Existing Alias",
"DialogMessage_AliasExistsMessage": "This alias is already in use.",
"DialogMessage_InvalidAliasTitle": "Invalid Alias",
"DialogMessage_InvalidAliasMessage": "This alias is not valid. Make sure all addresses of the alias are valid e-mail addresses.",
"DialogMessage_CantDeleteRootAliasTitle": "Can't Delete Alias",
"DialogMessage_CantDeleteRootAliasMessage": "Root alias can't be deleted. This is your main identity associated with your account setup.",
"DialogMessage_AliasCreatedTitle": "Created New Alias",
"DialogMessage_AliasCreatedMessage": "New alias is succesfully created.",
"DialogMessage_CleanupFolderMessage": "Do you want to permanently delete all the mails in this folder?",
"DialogMessage_CleanupFolderTitle": "Cleanup Folder",
"DialogMessage_ComposerMissingRecipientMessage": "Message has no recipient.",
@@ -92,6 +101,12 @@
"DialogMessage_UnsubscribeConfirmationGoToWebsiteConfirmButton": "Go to website",
"DialogMessage_UnsubscribeConfirmationMailtoMessage": "Do you want to stop getting messages from {0}? Wino will unsubscribe for you by sending an email from your email account to {1}.",
"Dialog_DontAskAgain": "Don't ask again",
"CreateAccountAliasDialog_Title": "Create Account Alias",
"CreateAccountAliasDialog_Description": "Make sure your outgoing server allows sending mails from this alias.",
"CreateAccountAliasDialog_AliasAddress": "Address",
"CreateAccountAliasDialog_AliasAddressPlaceholder": "eg. support@mydomain.com",
"CreateAccountAliasDialog_ReplyToAddress": "Reply-To Address",
"CreateAccountAliasDialog_ReplyToAddressPlaceholder": "admin@mydomain.com",
"DiscordChannelDisclaimerMessage": "Wino doesn't have it's own Discord server, but special 'wino-mail' channel is hosted at 'Developer Sanctuary' server.\nTo get the updates about Wino please join Developer Sanctuary server and follow 'wino-mail' channel under 'Community Projects'\n\nYou will be directed to server URL since Discord doesn't support channel invites.",
"DiscordChannelDisclaimerTitle": "Important Discord Information",
"Draft": "Draft",
@@ -113,6 +128,7 @@
"Exception_CustomThemeMissingName": "You must provide a name.",
"Exception_CustomThemeMissingWallpaper": "You must provide a custom background image.",
"Exception_FailedToSynchronizeFolders": "Failed to synchronize folders",
"Exception_FailedToSynchronizeProfileInformation": "Failed to synchronize profile information",
"Exception_GoogleAuthCallbackNull": "Callback uri is null on activation.",
"Exception_GoogleAuthCorruptedCode": "Corrupted authorization response.",
"Exception_GoogleAuthError": "OAuth authorization error: {0}",
@@ -253,6 +269,7 @@
"Info_UnsubscribeLinkInvalidMessage": "This unsubscribe link is invalid. Failed to unsubscribe from the list.",
"Info_UnsubscribeSuccessMessage": "Successfully unsubscribed from {0}.",
"Info_UnsubscribeErrorMessage": "Failed to unsubscribe",
"Info_CantDeletePrimaryAliasMessage": "Primary alias can't be deleted. Please change your alias before deleting this one",
"ImapAdvancedSetupDialog_AuthenticationMethod": "Authentication method",
"ImapAdvancedSetupDialog_ConnectionSecurity": "Connection security",
"ImapAuthenticationMethod_Auto": "Auto",
@@ -396,6 +413,8 @@
"SettingsFolderSync_Title": "Folder Synchronization",
"SettingsFolderOptions_Title": "Folder Configuration",
"SettingsFolderOptions_Description": "Change individual folder settings like enable/disable sync or show/hide unread badge.",
"SettingsManageAliases_Title": "Aliases",
"SettingsManageAliases_Description": "See e-mail aliases assigned for this account, update or delete them.",
"SettingsHoverActionCenter": "Center Action",
"SettingsHoverActionLeft": "Left Action",
"SettingsHoverActionRight": "Right Action",
@@ -406,6 +425,11 @@
"SettingsLanguageTime_Title": "Language & Time",
"SettingsLanguageTime_Description": "Wino display language, preferred time format.",
"CategoriesFolderNameOverride": "Categories",
"AccountAlias_Column_Verified": "Verified",
"AccountAlias_Column_Alias": "Alias",
"AccountAlias_Column_IsPrimaryAlias": "Primary",
"AccountAlias_Disclaimer_FirstLine": "Wino can only import aliases for your Gmail accounts.",
"AccountAlias_Disclaimer_SecondLine": "If you want to use aliases for your Outlook or IMAP account, please add them yourself.",
"MoreFolderNameOverride": "More",
"SettingsOptions_Title": "Settings",
"SettingsLinkAccounts_Description": "Merge multiple accounts into one. See mails from one Inbox together.",

View File

@@ -38,6 +38,11 @@ namespace Wino.Core.Domain
/// </summary>
public static string AccountCreationDialog_SigninIn => Resources.GetTranslatedString(@"AccountCreationDialog_SigninIn");
/// <summary>
/// Fetching profile details.
/// </summary>
public static string AccountCreationDialog_FetchingProfileInformation => Resources.GetTranslatedString(@"AccountCreationDialog_FetchingProfileInformation");
/// <summary>
/// Account Name
/// </summary>
@@ -343,6 +348,46 @@ namespace Wino.Core.Domain
/// </summary>
public static string DialogMessage_AccountLimitTitle => Resources.GetTranslatedString(@"DialogMessage_AccountLimitTitle");
/// <summary>
/// Existing Alias
/// </summary>
public static string DialogMessage_AliasExistsTitle => Resources.GetTranslatedString(@"DialogMessage_AliasExistsTitle");
/// <summary>
/// This alias is already in use.
/// </summary>
public static string DialogMessage_AliasExistsMessage => Resources.GetTranslatedString(@"DialogMessage_AliasExistsMessage");
/// <summary>
/// Invalid Alias
/// </summary>
public static string DialogMessage_InvalidAliasTitle => Resources.GetTranslatedString(@"DialogMessage_InvalidAliasTitle");
/// <summary>
/// This alias is not valid. Make sure all addresses of the alias are valid e-mail addresses.
/// </summary>
public static string DialogMessage_InvalidAliasMessage => Resources.GetTranslatedString(@"DialogMessage_InvalidAliasMessage");
/// <summary>
/// Can't Delete Alias
/// </summary>
public static string DialogMessage_CantDeleteRootAliasTitle => Resources.GetTranslatedString(@"DialogMessage_CantDeleteRootAliasTitle");
/// <summary>
/// Root alias can't be deleted. This is your main identity associated with your account setup.
/// </summary>
public static string DialogMessage_CantDeleteRootAliasMessage => Resources.GetTranslatedString(@"DialogMessage_CantDeleteRootAliasMessage");
/// <summary>
/// Created New Alias
/// </summary>
public static string DialogMessage_AliasCreatedTitle => Resources.GetTranslatedString(@"DialogMessage_AliasCreatedTitle");
/// <summary>
/// New alias is succesfully created.
/// </summary>
public static string DialogMessage_AliasCreatedMessage => Resources.GetTranslatedString(@"DialogMessage_AliasCreatedMessage");
/// <summary>
/// Do you want to permanently delete all the mails in this folder?
/// </summary>
@@ -434,7 +479,7 @@ namespace Wino.Core.Domain
public static string DialogMessage_UnlinkAccountsConfirmationTitle => Resources.GetTranslatedString(@"DialogMessage_UnlinkAccountsConfirmationTitle");
/// <summary>
/// Missin Subject
/// Missing Subject
/// </summary>
public static string DialogMessage_EmptySubjectConfirmation => Resources.GetTranslatedString(@"DialogMessage_EmptySubjectConfirmation");
@@ -483,6 +528,36 @@ namespace Wino.Core.Domain
/// </summary>
public static string Dialog_DontAskAgain => Resources.GetTranslatedString(@"Dialog_DontAskAgain");
/// <summary>
/// Create Account Alias
/// </summary>
public static string CreateAccountAliasDialog_Title => Resources.GetTranslatedString(@"CreateAccountAliasDialog_Title");
/// <summary>
/// Make sure your outgoing server allows sending mails from this alias.
/// </summary>
public static string CreateAccountAliasDialog_Description => Resources.GetTranslatedString(@"CreateAccountAliasDialog_Description");
/// <summary>
/// Address
/// </summary>
public static string CreateAccountAliasDialog_AliasAddress => Resources.GetTranslatedString(@"CreateAccountAliasDialog_AliasAddress");
/// <summary>
/// eg. support@mydomain.com
/// </summary>
public static string CreateAccountAliasDialog_AliasAddressPlaceholder => Resources.GetTranslatedString(@"CreateAccountAliasDialog_AliasAddressPlaceholder");
/// <summary>
/// Reply-To Address
/// </summary>
public static string CreateAccountAliasDialog_ReplyToAddress => Resources.GetTranslatedString(@"CreateAccountAliasDialog_ReplyToAddress");
/// <summary>
/// admin@mydomain.com
/// </summary>
public static string CreateAccountAliasDialog_ReplyToAddressPlaceholder => Resources.GetTranslatedString(@"CreateAccountAliasDialog_ReplyToAddressPlaceholder");
/// <summary>
/// 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.
/// </summary>
@@ -588,6 +663,11 @@ namespace Wino.Core.Domain
/// </summary>
public static string Exception_FailedToSynchronizeFolders => Resources.GetTranslatedString(@"Exception_FailedToSynchronizeFolders");
/// <summary>
/// Failed to synchronize profile information
/// </summary>
public static string Exception_FailedToSynchronizeProfileInformation => Resources.GetTranslatedString(@"Exception_FailedToSynchronizeProfileInformation");
/// <summary>
/// Callback uri is null on activation.
/// </summary>
@@ -1288,6 +1368,11 @@ namespace Wino.Core.Domain
/// </summary>
public static string Info_UnsubscribeErrorMessage => Resources.GetTranslatedString(@"Info_UnsubscribeErrorMessage");
/// <summary>
/// Primary alias can't be deleted. Please change your alias before deleting this one
/// </summary>
public static string Info_CantDeletePrimaryAliasMessage => Resources.GetTranslatedString(@"Info_CantDeletePrimaryAliasMessage");
/// <summary>
/// Authentication method
/// </summary>
@@ -2003,6 +2088,16 @@ namespace Wino.Core.Domain
/// </summary>
public static string SettingsFolderOptions_Description => Resources.GetTranslatedString(@"SettingsFolderOptions_Description");
/// <summary>
/// Aliases
/// </summary>
public static string SettingsManageAliases_Title => Resources.GetTranslatedString(@"SettingsManageAliases_Title");
/// <summary>
/// See e-mail aliases assigned for this account, update or delete them.
/// </summary>
public static string SettingsManageAliases_Description => Resources.GetTranslatedString(@"SettingsManageAliases_Description");
/// <summary>
/// Center Action
/// </summary>
@@ -2053,6 +2148,31 @@ namespace Wino.Core.Domain
/// </summary>
public static string CategoriesFolderNameOverride => Resources.GetTranslatedString(@"CategoriesFolderNameOverride");
/// <summary>
/// Verified
/// </summary>
public static string AccountAlias_Column_Verified => Resources.GetTranslatedString(@"AccountAlias_Column_Verified");
/// <summary>
/// Alias
/// </summary>
public static string AccountAlias_Column_Alias => Resources.GetTranslatedString(@"AccountAlias_Column_Alias");
/// <summary>
/// Primary
/// </summary>
public static string AccountAlias_Column_IsPrimaryAlias => Resources.GetTranslatedString(@"AccountAlias_Column_IsPrimaryAlias");
/// <summary>
/// Wino can only import aliases for your Gmail accounts.
/// </summary>
public static string AccountAlias_Disclaimer_FirstLine => Resources.GetTranslatedString(@"AccountAlias_Disclaimer_FirstLine");
/// <summary>
/// If you want to use aliases for your Outlook or IMAP account, please add them yourself.
/// </summary>
public static string AccountAlias_Disclaimer_SecondLine => Resources.GetTranslatedString(@"AccountAlias_Disclaimer_SecondLine");
/// <summary>
/// More
/// </summary>