Managing account aliases and profile synchronization for outlook and gmail.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
ManuelSetupWaiting,
|
||||
TestingConnection,
|
||||
AutoDiscoverySetup,
|
||||
AutoDiscoveryInProgress
|
||||
AutoDiscoveryInProgress,
|
||||
FetchingProfileInformation
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,5 +23,6 @@
|
||||
LanguageTimePage,
|
||||
AppPreferencesPage,
|
||||
SettingOptionsPage,
|
||||
AliasManagementPage
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
9
Wino.Core.Domain/Interfaces/ICreateAccountAliasDialog.cs
Normal file
9
Wino.Core.Domain/Interfaces/ICreateAccountAliasDialog.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Wino.Core.Domain.Entities;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces
|
||||
{
|
||||
public interface ICreateAccountAliasDialog
|
||||
{
|
||||
public MailAccountAlias CreatedAccountAlias { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = "");
|
||||
}
|
||||
|
||||
9
Wino.Core.Domain/Models/Accounts/ProfileInformation.cs
Normal file
9
Wino.Core.Domain/Models/Accounts/ProfileInformation.cs
Normal 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);
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.",
|
||||
|
||||
122
Wino.Core.Domain/Translator.Designer.cs
generated
122
Wino.Core.Domain/Translator.Designer.cs
generated
@@ -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>
|
||||
|
||||
@@ -205,21 +205,22 @@ namespace Wino.Core.Extensions
|
||||
};
|
||||
}
|
||||
|
||||
public static List<MailAccountAlias> GetMailAliases(this ListSendAsResponse response, MailAccount currentAccount)
|
||||
public static List<MailAccountAlias> GetMailAliases(this ListSendAsResponse response, List<MailAccountAlias> currentAliases, MailAccount account)
|
||||
{
|
||||
if (response == null || response.SendAs == null) return currentAccount.Aliases;
|
||||
if (response == null || response.SendAs == null) return currentAliases;
|
||||
|
||||
var remoteAliases = response.SendAs.Select(a => new MailAccountAlias()
|
||||
{
|
||||
AccountId = currentAccount.Id,
|
||||
AccountId = account.Id,
|
||||
AliasAddress = a.SendAsEmail,
|
||||
IsPrimary = a.IsPrimary.GetValueOrDefault(),
|
||||
ReplyToAddress = string.IsNullOrEmpty(a.ReplyToAddress) ? currentAccount.Address : a.ReplyToAddress,
|
||||
ReplyToAddress = string.IsNullOrEmpty(a.ReplyToAddress) ? account.Address : a.ReplyToAddress,
|
||||
IsVerified = string.IsNullOrEmpty(a.VerificationStatus) ? true : a.VerificationStatus == "accepted",
|
||||
IsRootAlias = account.Address == a.SendAsEmail,
|
||||
Id = Guid.NewGuid()
|
||||
}).ToList();
|
||||
|
||||
return EntityExtensions.GetFinalAliasList(currentAccount.Aliases, remoteAliases);
|
||||
return EntityExtensions.GetFinalAliasList(currentAliases, remoteAliases);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ namespace Wino.Core.Integration.Processors
|
||||
/// <returns>All folders.</returns>
|
||||
Task<List<MailItemFolder>> GetLocalFoldersAsync(Guid accountId);
|
||||
|
||||
Task<List<MailAccountAlias>> GetAccountAliasesAsync(Guid accountId);
|
||||
|
||||
Task<List<MailItemFolder>> GetSynchronizationFoldersAsync(SynchronizationOptions options);
|
||||
|
||||
Task<bool> MapLocalDraftAsync(Guid accountId, Guid localDraftCopyUniqueId, string newMailCopyId, string newDraftId, string newThreadId);
|
||||
@@ -179,6 +181,9 @@ namespace Wino.Core.Integration.Processors
|
||||
=> AccountService.UpdateAccountAsync(account);
|
||||
|
||||
public Task UpdateAccountAliasesAsync(Guid accountId, List<MailAccountAlias> aliases)
|
||||
=> AccountService.UpdateAccountAliases(accountId, aliases);
|
||||
=> AccountService.UpdateAccountAliasesAsync(accountId, aliases);
|
||||
|
||||
public Task<List<MailAccountAlias>> GetAccountAliasesAsync(Guid accountId)
|
||||
=> AccountService.GetAccountAliasesAsync(accountId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Wino.Core.MenuItems
|
||||
public string Base64ProfilePicture
|
||||
{
|
||||
get => Parameter.Name;
|
||||
set => SetProperty(Parameter.ProfilePictureBase64, value, Parameter, (u, n) => u.ProfilePictureBase64 = n);
|
||||
set => SetProperty(Parameter.Base64ProfilePictureData, value, Parameter, (u, n) => u.Base64ProfilePictureData = n);
|
||||
}
|
||||
|
||||
public IEnumerable<MailAccount> HoldingAccounts => new List<MailAccount> { Parameter };
|
||||
@@ -65,7 +65,7 @@ namespace Wino.Core.MenuItems
|
||||
Parameter = account;
|
||||
AccountName = account.Name;
|
||||
AttentionReason = account.AttentionReason;
|
||||
Base64ProfilePicture = account.ProfilePictureBase64;
|
||||
Base64ProfilePicture = account.Base64ProfilePictureData;
|
||||
|
||||
if (SubMenuItems == null) return;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ using SqlKata;
|
||||
using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
using Wino.Core.Extensions;
|
||||
using Wino.Messaging.Client.Accounts;
|
||||
using Wino.Messaging.UI;
|
||||
@@ -226,43 +227,37 @@ namespace Wino.Core.Services
|
||||
if (account.MergedInboxId != null)
|
||||
account.MergedInbox = await GetMergedInboxInformationAsync(account.MergedInboxId.Value);
|
||||
|
||||
// Load aliases
|
||||
account.Aliases = await GetAccountAliases(account.Id, account.Address);
|
||||
|
||||
account.Preferences = await GetAccountPreferencesAsync(account.Id);
|
||||
}
|
||||
|
||||
return accounts;
|
||||
}
|
||||
|
||||
private async Task<List<MailAccountAlias>> GetAccountAliases(Guid accountId, string primaryAccountAddress)
|
||||
public async Task CreateRootAliasAsync(Guid accountId, string address)
|
||||
{
|
||||
// By default all accounts must have at least 1 primary alias to create drafts for.
|
||||
// If there's no alias, create one from the existing account address. Migration doesn't exists to create one for older messages.
|
||||
|
||||
var aliases = await Connection
|
||||
.Table<MailAccountAlias>()
|
||||
.Where(a => a.AccountId == accountId)
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!aliases.Any())
|
||||
var rootAlias = new MailAccountAlias()
|
||||
{
|
||||
var primaryAccountAlias = new MailAccountAlias()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
AccountId = accountId,
|
||||
IsPrimary = true,
|
||||
AliasAddress = primaryAccountAddress,
|
||||
ReplyToAddress = primaryAccountAddress,
|
||||
IsVerified = true,
|
||||
};
|
||||
AccountId = accountId,
|
||||
AliasAddress = address,
|
||||
IsPrimary = true,
|
||||
IsRootAlias = true,
|
||||
IsVerified = true,
|
||||
ReplyToAddress = address,
|
||||
Id = Guid.NewGuid()
|
||||
};
|
||||
|
||||
await Connection.InsertAsync(primaryAccountAlias).ConfigureAwait(false);
|
||||
aliases.Add(primaryAccountAlias);
|
||||
}
|
||||
await Connection.InsertAsync(rootAlias).ConfigureAwait(false);
|
||||
|
||||
return aliases;
|
||||
Log.Information("Created root alias for the account {AccountId}", accountId);
|
||||
}
|
||||
|
||||
public async Task<List<MailAccountAlias>> GetAccountAliasesAsync(Guid accountId)
|
||||
{
|
||||
var query = new Query(nameof(MailAccountAlias))
|
||||
.Where(nameof(MailAccountAlias.AccountId), accountId)
|
||||
.OrderByDesc(nameof(MailAccountAlias.IsRootAlias));
|
||||
|
||||
return await Connection.QueryAsync<MailAccountAlias>(query.GetRawQuery()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private Task<MergedInbox> GetMergedInboxInformationAsync(Guid mergedInboxId)
|
||||
@@ -277,6 +272,7 @@ namespace Wino.Core.Services
|
||||
await Connection.Table<TokenInformation>().Where(a => a.AccountId == account.Id).DeleteAsync();
|
||||
await Connection.Table<MailItemFolder>().DeleteAsync(a => a.MailAccountId == account.Id);
|
||||
await Connection.Table<AccountSignature>().DeleteAsync(a => a.MailAccountId == account.Id);
|
||||
await Connection.Table<MailAccountAlias>().DeleteAsync(a => a.AccountId == account.Id);
|
||||
|
||||
// Account belongs to a merged inbox.
|
||||
// In case of there'll be a single account in the merged inbox, remove the merged inbox as well.
|
||||
@@ -327,6 +323,19 @@ namespace Wino.Core.Services
|
||||
ReportUIChange(new AccountRemovedMessage(account));
|
||||
}
|
||||
|
||||
public async Task UpdateProfileInformationAsync(Guid accountId, ProfileInformation profileInformation)
|
||||
{
|
||||
var account = await GetAccountAsync(accountId).ConfigureAwait(false);
|
||||
|
||||
if (account != null)
|
||||
{
|
||||
account.SenderName = profileInformation.SenderName;
|
||||
account.Base64ProfilePictureData = profileInformation.Base64ProfilePictureData;
|
||||
|
||||
await UpdateAccountAsync(account).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MailAccount> GetAccountAsync(Guid accountId)
|
||||
{
|
||||
var account = await Connection.Table<MailAccount>().FirstOrDefaultAsync(a => a.Id == accountId);
|
||||
@@ -359,7 +368,7 @@ namespace Wino.Core.Services
|
||||
ReportUIChange(new AccountUpdatedMessage(account));
|
||||
}
|
||||
|
||||
public async Task UpdateAccountAliases(Guid accountId, List<MailAccountAlias> aliases)
|
||||
public async Task UpdateAccountAliasesAsync(Guid accountId, List<MailAccountAlias> aliases)
|
||||
{
|
||||
// Delete existing ones.
|
||||
await Connection.Table<MailAccountAlias>().DeleteAsync(a => a.AccountId == accountId).ConfigureAwait(false);
|
||||
@@ -371,6 +380,17 @@ namespace Wino.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteAccountAliasAsync(Guid aliasId)
|
||||
{
|
||||
// Create query to delete alias.
|
||||
|
||||
var query = new Query("MailAccountAlias")
|
||||
.Where("Id", aliasId)
|
||||
.AsDelete();
|
||||
|
||||
await Connection.ExecuteAsync(query.GetRawQuery()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task CreateAccountAsync(MailAccount account, TokenInformation tokenInformation, CustomServerInformation customServerInformation)
|
||||
{
|
||||
Guard.IsNotNull(account);
|
||||
@@ -424,7 +444,7 @@ namespace Wino.Core.Services
|
||||
// Outlook token cache is managed by MSAL.
|
||||
// Don't save it to database.
|
||||
|
||||
if (tokenInformation != null && account.ProviderType != MailProviderType.Outlook)
|
||||
if (tokenInformation != null && (account.ProviderType != MailProviderType.Outlook || account.ProviderType == MailProviderType.Office365))
|
||||
await Connection.InsertAsync(tokenInformation);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ 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.MailItem;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.Integration;
|
||||
@@ -71,10 +72,16 @@ namespace Wino.Core.Synchronizers
|
||||
public abstract Task ExecuteNativeRequestsAsync(IEnumerable<IRequestBundle<TBaseRequest>> batchedRequests, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Refreshed remote mail account profile if possible.
|
||||
/// Aliases, profile pictures, mailbox settings will be handled in this step.
|
||||
/// Refreshes remote mail account profile if possible.
|
||||
/// Profile picture, sender name and mailbox settings (todo) will be handled in this step.
|
||||
/// </summary>
|
||||
protected virtual Task SynchronizeProfileInformationAsync() => Task.CompletedTask;
|
||||
public virtual Task<ProfileInformation> SynchronizeProfileInformationAsync() => default;
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the aliases of the account.
|
||||
/// Only available for Gmail right now.
|
||||
/// </summary>
|
||||
protected virtual Task SynchronizeAliasesAsync() => Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the base64 encoded profile picture of the account from the given URL.
|
||||
@@ -100,6 +107,33 @@ namespace Wino.Core.Synchronizers
|
||||
/// <returns>Synchronization result that contains summary of the sync.</returns>
|
||||
protected abstract Task<SynchronizationResult> SynchronizeInternalAsync(SynchronizationOptions options, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Safely updates account's profile information.
|
||||
/// Database changes are reflected after this call.
|
||||
/// Null returns mean that the operation failed.
|
||||
/// </summary>
|
||||
private async Task<ProfileInformation> SynchronizeProfileInformationInternalAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var profileInformation = await SynchronizeProfileInformationAsync();
|
||||
|
||||
if (profileInformation != null)
|
||||
{
|
||||
Account.SenderName = profileInformation.SenderName;
|
||||
Account.Base64ProfilePictureData = profileInformation.Base64ProfilePictureData;
|
||||
}
|
||||
|
||||
return profileInformation;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to update profile information for account '{Name}'", Account.Name);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Batches network requests, executes them, and does the needed synchronization after the batch request execution.
|
||||
/// </summary>
|
||||
@@ -139,12 +173,16 @@ namespace Wino.Core.Synchronizers
|
||||
|
||||
await synchronizationSemaphore.WaitAsync(activeSynchronizationCancellationToken);
|
||||
|
||||
if (options.Type == SynchronizationType.Full)
|
||||
if (options.Type == SynchronizationType.UpdateProfile)
|
||||
{
|
||||
// Refresh profile information and mailbox settings on full synchronization.
|
||||
// Refresh profile information on full synchronization.
|
||||
// Exceptions here is not critical. Therefore, they are ignored.
|
||||
|
||||
await SynchronizeProfileInformationAsync();
|
||||
var newprofileInformation = await SynchronizeProfileInformationInternalAsync();
|
||||
|
||||
if (newprofileInformation == null) return SynchronizationResult.Failed;
|
||||
|
||||
return SynchronizationResult.Completed(null, newprofileInformation);
|
||||
}
|
||||
|
||||
// Let servers to finish their job. Sometimes the servers doesn't respond immediately.
|
||||
|
||||
@@ -19,6 +19,7 @@ using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Exceptions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Requests;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
@@ -68,67 +69,45 @@ namespace Wino.Core.Synchronizers
|
||||
|
||||
public ConfigurableHttpClient CreateHttpClient(CreateHttpClientArgs args) => _googleHttpClient;
|
||||
|
||||
protected override async Task SynchronizeProfileInformationAsync()
|
||||
public override async Task<ProfileInformation> SynchronizeProfileInformationAsync()
|
||||
{
|
||||
// Gmail profile info synchronizes Sender Name, Alias and Profile Picture.
|
||||
var profileRequest = _peopleService.People.Get("people/me");
|
||||
profileRequest.PersonFields = "names,photos";
|
||||
|
||||
try
|
||||
string senderName = string.Empty, base64ProfilePicture = string.Empty;
|
||||
|
||||
var userProfile = await profileRequest.ExecuteAsync();
|
||||
|
||||
senderName = userProfile.Names?.FirstOrDefault()?.DisplayName ?? Account.SenderName;
|
||||
|
||||
var profilePicture = userProfile.Photos?.FirstOrDefault()?.Url ?? string.Empty;
|
||||
|
||||
if (!string.IsNullOrEmpty(profilePicture))
|
||||
{
|
||||
var profileRequest = _peopleService.People.Get("people/me");
|
||||
profileRequest.PersonFields = "names,photos";
|
||||
|
||||
string senderName = Account.SenderName, base64ProfilePicture = Account.ProfilePictureBase64;
|
||||
|
||||
var userProfile = await profileRequest.ExecuteAsync();
|
||||
|
||||
senderName = userProfile.Names?.FirstOrDefault()?.DisplayName ?? Account.SenderName;
|
||||
|
||||
var profilePicture = userProfile.Photos?.FirstOrDefault()?.Url ?? string.Empty;
|
||||
|
||||
if (!string.IsNullOrEmpty(profilePicture))
|
||||
{
|
||||
base64ProfilePicture = await GetProfilePictureBase64EncodedAsync(profilePicture).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
bool shouldUpdateAccountProfile = (!string.IsNullOrEmpty(senderName) && Account.SenderName != senderName)
|
||||
|| (!string.IsNullOrEmpty(profilePicture) && Account.ProfilePictureBase64 != base64ProfilePicture);
|
||||
|
||||
if (!string.IsNullOrEmpty(senderName) && Account.SenderName != senderName)
|
||||
{
|
||||
Account.SenderName = senderName;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(profilePicture) && Account.ProfilePictureBase64 != base64ProfilePicture)
|
||||
{
|
||||
Account.ProfilePictureBase64 = base64ProfilePicture;
|
||||
}
|
||||
|
||||
// Sync aliases
|
||||
|
||||
var sendAsListRequest = _gmailService.Users.Settings.SendAs.List("me");
|
||||
var sendAsListResponse = await sendAsListRequest.ExecuteAsync();
|
||||
|
||||
var updatedAliases = sendAsListResponse.GetMailAliases(Account);
|
||||
|
||||
bool shouldUpdateAliases =
|
||||
Account.Aliases.Any(a => updatedAliases.Any(b => a.Id == b.Id) == false) ||
|
||||
updatedAliases.Any(a => Account.Aliases.Any(b => a.Id == b.Id) == false);
|
||||
|
||||
if (shouldUpdateAliases)
|
||||
{
|
||||
Account.Aliases = updatedAliases;
|
||||
|
||||
await _gmailChangeProcessor.UpdateAccountAliasesAsync(Account.Id, updatedAliases);
|
||||
}
|
||||
|
||||
if (shouldUpdateAccountProfile)
|
||||
{
|
||||
await _gmailChangeProcessor.UpdateAccountAsync(Account).ConfigureAwait(false);
|
||||
}
|
||||
base64ProfilePicture = await GetProfilePictureBase64EncodedAsync(profilePicture).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
return new ProfileInformation(senderName, base64ProfilePicture);
|
||||
}
|
||||
|
||||
protected override async Task SynchronizeAliasesAsync()
|
||||
{
|
||||
// Sync aliases
|
||||
|
||||
var sendAsListRequest = _gmailService.Users.Settings.SendAs.List("me");
|
||||
var sendAsListResponse = await sendAsListRequest.ExecuteAsync();
|
||||
|
||||
var localAliases = await _gmailChangeProcessor.GetAccountAliasesAsync(Account.Id).ConfigureAwait(false);
|
||||
|
||||
var updatedAliases = sendAsListResponse.GetMailAliases(localAliases, Account);
|
||||
|
||||
bool shouldUpdateAliases =
|
||||
localAliases.Any(a => updatedAliases.Any(b => a.Id == b.Id) == false) ||
|
||||
updatedAliases.Any(a => localAliases.Any(b => a.Id == b.Id) == false);
|
||||
|
||||
if (shouldUpdateAliases)
|
||||
{
|
||||
Logger.Error(ex, "Error while synchronizing profile information for {Name}", Account.Name);
|
||||
await _gmailChangeProcessor.UpdateAccountAliasesAsync(Account.Id, updatedAliases);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Exceptions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Requests;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
@@ -479,70 +480,28 @@ namespace Wino.Core.Synchronizers
|
||||
/// <returns>Base64 encoded profile picture.</returns>
|
||||
private async Task<string> GetUserProfilePictureAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var photoStream = await _graphClient.Me.Photos["48x48"].Content.GetAsync();
|
||||
var photoStream = await _graphClient.Me.Photos["48x48"].Content.GetAsync();
|
||||
|
||||
using var memoryStream = new MemoryStream();
|
||||
await photoStream.CopyToAsync(memoryStream);
|
||||
var byteArray = memoryStream.ToArray();
|
||||
using var memoryStream = new MemoryStream();
|
||||
await photoStream.CopyToAsync(memoryStream);
|
||||
var byteArray = memoryStream.ToArray();
|
||||
|
||||
return Convert.ToBase64String(byteArray);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error occurred while getting user profile picture.");
|
||||
return string.Empty;
|
||||
}
|
||||
return Convert.ToBase64String(byteArray);
|
||||
}
|
||||
|
||||
private async Task<string> GetSenderNameAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userInfo = await _graphClient.Users["me"].GetAsync();
|
||||
var userInfo = await _graphClient.Users["me"].GetAsync();
|
||||
|
||||
return userInfo.DisplayName;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to get sender name.");
|
||||
return string.Empty;
|
||||
}
|
||||
return userInfo.DisplayName;
|
||||
}
|
||||
|
||||
protected override async Task SynchronizeProfileInformationAsync()
|
||||
public override async Task<ProfileInformation> SynchronizeProfileInformationAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Outlook profile info synchronizes Sender Name and Profile Picture.
|
||||
string senderName = Account.SenderName, base64ProfilePicture = Account.ProfilePictureBase64;
|
||||
var profilePictureData = await GetUserProfilePictureAsync().ConfigureAwait(false);
|
||||
var senderName = await GetSenderNameAsync().ConfigureAwait(false);
|
||||
|
||||
var profilePictureData = await GetUserProfilePictureAsync().ConfigureAwait(false);
|
||||
senderName = await GetSenderNameAsync().ConfigureAwait(false);
|
||||
|
||||
bool shouldUpdateAccountProfile = (!string.IsNullOrEmpty(senderName) && Account.SenderName != senderName)
|
||||
|| (!string.IsNullOrEmpty(profilePictureData) && Account.ProfilePictureBase64 != base64ProfilePicture);
|
||||
|
||||
if (!string.IsNullOrEmpty(profilePictureData) && Account.ProfilePictureBase64 != profilePictureData)
|
||||
{
|
||||
Account.ProfilePictureBase64 = profilePictureData;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(senderName) && Account.SenderName != senderName)
|
||||
{
|
||||
Account.SenderName = senderName;
|
||||
}
|
||||
|
||||
if (shouldUpdateAccountProfile)
|
||||
{
|
||||
await _outlookChangeProcessor.UpdateAccountAsync(Account).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to synchronize profile information for {Name}", Account.Name);
|
||||
}
|
||||
return new ProfileInformation(senderName, profilePictureData);
|
||||
}
|
||||
|
||||
#region Mail Integration
|
||||
|
||||
@@ -56,7 +56,11 @@ namespace Wino.Mail.ViewModels
|
||||
|
||||
[RelayCommand]
|
||||
private void EditSignature()
|
||||
=> Messenger.Send(new BreadcrumbNavigationRequested("Signature", WinoPage.SignatureManagementPage, Account.Id));
|
||||
=> Messenger.Send(new BreadcrumbNavigationRequested(Translator.SettingsSignature_Title, WinoPage.SignatureManagementPage, Account.Id));
|
||||
|
||||
[RelayCommand]
|
||||
private void EditAliases()
|
||||
=> Messenger.Send(new BreadcrumbNavigationRequested(Translator.SettingsManageAliases_Title, WinoPage.AliasManagementPage, Account.Id));
|
||||
|
||||
public Task FolderSyncToggledAsync(IMailItemFolder folderStructure, bool isEnabled)
|
||||
=> _folderService.ChangeFolderSynchronizationStateAsync(folderStructure.Id, isEnabled);
|
||||
|
||||
@@ -154,15 +154,12 @@ namespace Wino.Mail.ViewModels
|
||||
{
|
||||
creationDialog = _dialogService.GetAccountCreationDialog(accountCreationDialogResult.ProviderType);
|
||||
|
||||
// _accountService.ExternalAuthenticationAuthenticator = _authenticationProvider.GetAuthenticator(accountCreationDialogResult.ProviderType);
|
||||
|
||||
CustomServerInformation customServerInformation = null;
|
||||
|
||||
createdAccount = new MailAccount()
|
||||
{
|
||||
ProviderType = accountCreationDialogResult.ProviderType,
|
||||
Name = accountCreationDialogResult.AccountName,
|
||||
SenderName = accountCreationDialogResult.SenderName,
|
||||
AccountColorHex = accountCreationDialogResult.AccountColorHex,
|
||||
Id = Guid.NewGuid()
|
||||
};
|
||||
@@ -208,30 +205,59 @@ namespace Wino.Mail.ViewModels
|
||||
await _accountService.CreateAccountAsync(createdAccount, tokenInformation, customServerInformation);
|
||||
|
||||
// Local account has been created.
|
||||
// Create new synchronizer and start synchronization.
|
||||
|
||||
// Start profile information synchronization.
|
||||
// Profile info is not updated in the database yet.
|
||||
|
||||
var profileSyncOptions = new SynchronizationOptions()
|
||||
{
|
||||
AccountId = createdAccount.Id,
|
||||
Type = SynchronizationType.UpdateProfile
|
||||
};
|
||||
|
||||
var profileSynchronizationResponse = await _winoServerConnectionManager.GetResponseAsync<SynchronizationResult, NewSynchronizationRequested>(new NewSynchronizationRequested(profileSyncOptions, SynchronizationSource.Client));
|
||||
|
||||
var profileSynchronizationResult = profileSynchronizationResponse.Data;
|
||||
|
||||
if (profileSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
throw new Exception(Translator.Exception_FailedToSynchronizeProfileInformation);
|
||||
|
||||
createdAccount.SenderName = profileSynchronizationResult.ProfileInformation.SenderName;
|
||||
createdAccount.Base64ProfilePictureData = profileSynchronizationResult.ProfileInformation.Base64ProfilePictureData;
|
||||
|
||||
await _accountService.UpdateProfileInformationAsync(createdAccount.Id, profileSynchronizationResult.ProfileInformation);
|
||||
|
||||
if (creationDialog is ICustomServerAccountCreationDialog customServerAccountCreationDialog)
|
||||
customServerAccountCreationDialog.ShowPreparingFolders();
|
||||
else
|
||||
creationDialog.State = AccountCreationDialogState.PreparingFolders;
|
||||
|
||||
var options = new SynchronizationOptions()
|
||||
// Start synchronizing folders.
|
||||
var folderSyncOptions = new SynchronizationOptions()
|
||||
{
|
||||
AccountId = createdAccount.Id,
|
||||
Type = SynchronizationType.FoldersOnly
|
||||
};
|
||||
|
||||
var synchronizationResultResponse = await _winoServerConnectionManager.GetResponseAsync<SynchronizationResult, NewSynchronizationRequested>(new NewSynchronizationRequested(options, SynchronizationSource.Client));
|
||||
var folderSynchronizationResponse = await _winoServerConnectionManager.GetResponseAsync<SynchronizationResult, NewSynchronizationRequested>(new NewSynchronizationRequested(folderSyncOptions, SynchronizationSource.Client));
|
||||
|
||||
var synchronizationResult = synchronizationResultResponse.Data;
|
||||
if (synchronizationResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
var folderSynchronizationResult = folderSynchronizationResponse.Data;
|
||||
|
||||
if (folderSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
throw new Exception(Translator.Exception_FailedToSynchronizeFolders);
|
||||
|
||||
// Check if Inbox folder is available for the account after synchronization.
|
||||
var isInboxAvailable = await _folderService.IsInboxAvailableForAccountAsync(createdAccount.Id);
|
||||
// Create root primary alias for the account.
|
||||
// This is the first alias for the account and it's primary.
|
||||
|
||||
if (!isInboxAvailable)
|
||||
throw new Exception(Translator.Exception_InboxNotAvailable);
|
||||
await _accountService.CreateRootAliasAsync(createdAccount.Id, createdAccount.Address);
|
||||
|
||||
// TODO: Temporary disabled. Is this even needed? Users can configure special folders manually later on if discovery fails.
|
||||
// Check if Inbox folder is available for the account after synchronization.
|
||||
|
||||
//var isInboxAvailable = await _folderService.IsInboxAvailableForAccountAsync(createdAccount.Id);
|
||||
|
||||
//if (!isInboxAvailable)
|
||||
// throw new Exception(Translator.Exception_InboxNotAvailable);
|
||||
|
||||
// Send changes to listeners.
|
||||
ReportUIChange(new AccountCreatedMessage(createdAccount));
|
||||
|
||||
115
Wino.Mail.ViewModels/AliasManagementPageViewModel.cs
Normal file
115
Wino.Mail.ViewModels/AliasManagementPageViewModel.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using EmailValidation;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
|
||||
namespace Wino.Mail.ViewModels
|
||||
{
|
||||
public partial class AliasManagementPageViewModel : BaseViewModel
|
||||
{
|
||||
private readonly IAccountService _accountService;
|
||||
|
||||
public MailAccount Account { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
private List<MailAccountAlias> accountAliases = [];
|
||||
|
||||
public AliasManagementPageViewModel(IDialogService dialogService, IAccountService accountService) : base(dialogService)
|
||||
{
|
||||
_accountService = accountService;
|
||||
}
|
||||
|
||||
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
{
|
||||
base.OnNavigatedTo(mode, parameters);
|
||||
|
||||
if (parameters is Guid accountId)
|
||||
Account = await _accountService.GetAccountAsync(accountId);
|
||||
|
||||
if (Account == null) return;
|
||||
|
||||
await LoadAliasesAsync();
|
||||
}
|
||||
|
||||
private async Task LoadAliasesAsync()
|
||||
{
|
||||
AccountAliases = await _accountService.GetAccountAliasesAsync(Account.Id);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SetAliasPrimaryAsync(MailAccountAlias alias)
|
||||
{
|
||||
if (alias.IsPrimary) return;
|
||||
|
||||
AccountAliases.ForEach(a =>
|
||||
{
|
||||
a.IsPrimary = a == alias;
|
||||
});
|
||||
|
||||
await _accountService.UpdateAccountAliasesAsync(Account.Id, AccountAliases);
|
||||
await LoadAliasesAsync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task AddNewAliasAsync()
|
||||
{
|
||||
var createdAliasDialog = await DialogService.ShowCreateAccountAliasDialogAsync();
|
||||
|
||||
if (createdAliasDialog.CreatedAccountAlias == null) return;
|
||||
|
||||
var newAlias = createdAliasDialog.CreatedAccountAlias;
|
||||
|
||||
// Check existence.
|
||||
if (AccountAliases.Any(a => a.AliasAddress == newAlias.AliasAddress))
|
||||
{
|
||||
await DialogService.ShowMessageAsync(Translator.DialogMessage_AliasExistsTitle, Translator.DialogMessage_AliasExistsMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate all addresses.
|
||||
if (!EmailValidator.Validate(newAlias.ReplyToAddress) || !EmailValidator.Validate(newAlias.AliasAddress))
|
||||
{
|
||||
await DialogService.ShowMessageAsync(Translator.DialogMessage_InvalidAliasMessage, Translator.DialogMessage_InvalidAliasTitle);
|
||||
return;
|
||||
}
|
||||
|
||||
newAlias.AccountId = Account.Id;
|
||||
|
||||
AccountAliases.Add(newAlias);
|
||||
|
||||
await _accountService.UpdateAccountAliasesAsync(Account.Id, AccountAliases);
|
||||
DialogService.InfoBarMessage(Translator.DialogMessage_AliasCreatedTitle, Translator.DialogMessage_AliasCreatedMessage, InfoBarMessageType.Success);
|
||||
|
||||
await LoadAliasesAsync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task DeleteAliasAsync(MailAccountAlias alias)
|
||||
{
|
||||
// Primary aliases can't be deleted.
|
||||
if (alias.IsPrimary)
|
||||
{
|
||||
await DialogService.ShowMessageAsync(Translator.Info_CantDeletePrimaryAliasMessage, Translator.GeneralTitle_Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
// Root aliases can't be deleted.
|
||||
if (alias.IsRootAlias)
|
||||
{
|
||||
await DialogService.ShowMessageAsync(Translator.DialogMessage_CantDeleteRootAliasTitle, Translator.DialogMessage_CantDeleteRootAliasMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
await _accountService.DeleteAccountAliasAsync(alias.Id);
|
||||
await LoadAliasesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,31 +3,21 @@ using Wino.Messaging.Client.Navigation;
|
||||
|
||||
namespace Wino.Mail.ViewModels.Data
|
||||
{
|
||||
public class BreadcrumbNavigationItemViewModel : ObservableObject
|
||||
public partial class BreadcrumbNavigationItemViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private string title;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool isActive;
|
||||
|
||||
public BreadcrumbNavigationRequested Request { get; set; }
|
||||
|
||||
public BreadcrumbNavigationItemViewModel(BreadcrumbNavigationRequested request, bool isActive)
|
||||
{
|
||||
Request = request;
|
||||
Title = request.PageTitle;
|
||||
|
||||
this.isActive = isActive;
|
||||
}
|
||||
|
||||
private string title;
|
||||
public string Title
|
||||
{
|
||||
get => title;
|
||||
set => SetProperty(ref title, value);
|
||||
}
|
||||
|
||||
private bool isActive;
|
||||
|
||||
public bool IsActive
|
||||
{
|
||||
get => isActive;
|
||||
set => SetProperty(ref isActive, value);
|
||||
IsActive = isActive;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="EmailValidation" Version="1.2.0" />
|
||||
<PackageReference Include="IsExternalInit" Version="1.0.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<Application x:Class="Wino.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Wino.Controls"
|
||||
xmlns:selectors="using:Wino.Selectors"
|
||||
xmlns:wino="using:Wino">
|
||||
<Application
|
||||
x:Class="Wino.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Wino.Controls"
|
||||
xmlns:selectors="using:Wino.Selectors"
|
||||
xmlns:wino="using:Wino">
|
||||
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
@@ -48,6 +49,14 @@
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</Style>
|
||||
|
||||
<!-- Custom Grid style for info panels. -->
|
||||
<Style TargetType="Grid" x:Key="InformationAreaGridStyle">
|
||||
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource DividerStrokeColorDefaultBrush}" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
<Setter Property="Padding" Value="16" />
|
||||
</Style>
|
||||
|
||||
<!-- Default StackPanel animation. -->
|
||||
<Style TargetType="StackPanel">
|
||||
<Setter Property="ChildrenTransitions">
|
||||
@@ -60,9 +69,10 @@
|
||||
</Style>
|
||||
|
||||
<!-- Default Style for ContentDialog -->
|
||||
<Style x:Key="WinoDialogStyle"
|
||||
BasedOn="{StaticResource DefaultContentDialogStyle}"
|
||||
TargetType="ContentDialog" />
|
||||
<Style
|
||||
x:Key="WinoDialogStyle"
|
||||
BasedOn="{StaticResource DefaultContentDialogStyle}"
|
||||
TargetType="ContentDialog" />
|
||||
|
||||
<!-- Settings Menu Item Template -->
|
||||
<Style TargetType="controls:SettingsMenuItemControl">
|
||||
@@ -74,63 +84,71 @@
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="controls:SettingsMenuItemControl">
|
||||
<Grid>
|
||||
<Button Padding="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Command="{TemplateBinding Command}"
|
||||
CommandParameter="{TemplateBinding CommandParameter}"
|
||||
IsEnabled="{TemplateBinding IsEnabled}"
|
||||
IsHitTestVisible="{TemplateBinding IsClickable}">
|
||||
<Grid Height="70"
|
||||
Padding="0,6,12,6"
|
||||
CornerRadius="4">
|
||||
<Button
|
||||
Padding="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Command="{TemplateBinding Command}"
|
||||
CommandParameter="{TemplateBinding CommandParameter}"
|
||||
IsEnabled="{TemplateBinding IsEnabled}"
|
||||
IsHitTestVisible="{TemplateBinding IsClickable}">
|
||||
<Grid
|
||||
Height="70"
|
||||
Padding="0,6,12,6"
|
||||
CornerRadius="4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="50" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ContentControl HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Content="{TemplateBinding Icon}" />
|
||||
<ContentControl
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Content="{TemplateBinding Icon}" />
|
||||
|
||||
<Grid Grid.Column="1"
|
||||
Margin="4,0"
|
||||
VerticalAlignment="Center"
|
||||
RowSpacing="3">
|
||||
<Grid
|
||||
Grid.Column="1"
|
||||
Margin="4,0"
|
||||
VerticalAlignment="Center"
|
||||
RowSpacing="3">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{TemplateBinding Title}" />
|
||||
<TextBlock Grid.Row="1"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{TemplateBinding Description}" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="{TemplateBinding Title}" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{TemplateBinding Description}" />
|
||||
</Grid>
|
||||
|
||||
<Viewbox Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Width="16"
|
||||
Height="16"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=IsNavigateIconVisible}">
|
||||
<Viewbox
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Width="16"
|
||||
Height="16"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=IsNavigateIconVisible}">
|
||||
<PathIcon Data="F1 M 5.029297 19.091797 L 14.111328 10 L 5.029297 0.908203 L 5.908203 0.029297 L 15.888672 10 L 5.908203 19.970703 Z " />
|
||||
</Viewbox>
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<ContentControl Grid.RowSpan="2"
|
||||
Margin="0,0,16,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Content="{TemplateBinding SideContent}"
|
||||
IsHitTestVisible="True"
|
||||
Visibility="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=IsNavigateIconVisible, Converter={StaticResource ReverseBooleanToVisibilityConverter}}" />
|
||||
<ContentControl
|
||||
Grid.RowSpan="2"
|
||||
Margin="0,0,16,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Content="{TemplateBinding SideContent}"
|
||||
IsHitTestVisible="True"
|
||||
Visibility="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=IsNavigateIconVisible, Converter={StaticResource ReverseBooleanToVisibilityConverter}}" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
@@ -188,16 +206,17 @@
|
||||
<Image Source="/Assets/FileTypes/type_other.png" />
|
||||
</DataTemplate>
|
||||
|
||||
<selectors:FileAttachmentTypeSelector x:Key="FileTypeIconSelector"
|
||||
Archive="{StaticResource ArchiveTemplate}"
|
||||
Executable="{StaticResource ExecutableTemplate}"
|
||||
HTML="{StaticResource HTMLTemplate}"
|
||||
Image="{StaticResource ImageTemplate}"
|
||||
None="{StaticResource NoneTemplate}"
|
||||
Other="{StaticResource OtherTemplate}"
|
||||
PDF="{StaticResource PDFTemplate}"
|
||||
RarArchive="{StaticResource RarTemplate}"
|
||||
Video="{StaticResource VideoTemplate}" />
|
||||
<selectors:FileAttachmentTypeSelector
|
||||
x:Key="FileTypeIconSelector"
|
||||
Archive="{StaticResource ArchiveTemplate}"
|
||||
Executable="{StaticResource ExecutableTemplate}"
|
||||
HTML="{StaticResource HTMLTemplate}"
|
||||
Image="{StaticResource ImageTemplate}"
|
||||
None="{StaticResource NoneTemplate}"
|
||||
Other="{StaticResource OtherTemplate}"
|
||||
PDF="{StaticResource PDFTemplate}"
|
||||
RarArchive="{StaticResource RarTemplate}"
|
||||
Video="{StaticResource VideoTemplate}" />
|
||||
</ResourceDictionary>
|
||||
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
|
||||
@@ -179,6 +179,7 @@ namespace Wino
|
||||
services.AddTransient(typeof(MergedAccountDetailsPageViewModel));
|
||||
services.AddTransient(typeof(LanguageTimePageViewModel));
|
||||
services.AddTransient(typeof(AppPreferencesPageViewModel));
|
||||
services.AddTransient(typeof(AliasManagementPageViewModel));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:xaml="using:Windows.UI.Xaml">
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:xaml="using:Windows.UI.Xaml">
|
||||
|
||||
<x:String x:Key="ThemeName">Mica</x:String>
|
||||
<x:Boolean x:Key="UseMica">True</x:Boolean>
|
||||
|
||||
@@ -47,6 +47,13 @@
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
<VisualState x:Name="FetchingProfileInformation">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="StatusText.Text" Value="{x:Bind domain:Translator.AccountCreationDialog_FetchingProfileInformation}" />
|
||||
<Setter Target="DialogIcon.Glyph" Value="F1 M 1.875 17.5 C 1.621094 17.5 1.380208 17.451172 1.152344 17.353516 C 0.924479 17.255859 0.724284 17.120768 0.551758 16.948242 C 0.379232 16.775717 0.244141 16.575521 0.146484 16.347656 C 0.048828 16.119791 0 15.878906 0 15.625 L 0 4.375 C 0 4.121094 0.048828 3.880209 0.146484 3.652344 C 0.244141 3.42448 0.379232 3.224285 0.551758 3.051758 C 0.724284 2.879232 0.924479 2.744141 1.152344 2.646484 C 1.380208 2.548828 1.621094 2.5 1.875 2.5 L 18.125 2.5 C 18.378906 2.5 18.619791 2.548828 18.847656 2.646484 C 19.07552 2.744141 19.275715 2.879232 19.448242 3.051758 C 19.620768 3.224285 19.755859 3.42448 19.853516 3.652344 C 19.951172 3.880209 20 4.121094 20 4.375 L 20 15.625 C 20 15.878906 19.951172 16.119791 19.853516 16.347656 C 19.755859 16.575521 19.620768 16.775717 19.448242 16.948242 C 19.275715 17.120768 19.07552 17.255859 18.847656 17.353516 C 18.619791 17.451172 18.378906 17.5 18.125 17.5 Z M 5 16.25 L 5 14.375 C 5 14.121094 5.048828 13.880209 5.146484 13.652344 C 5.244141 13.424479 5.379231 13.224284 5.551758 13.051758 C 5.724284 12.879232 5.924479 12.744141 6.152344 12.646484 C 6.380208 12.548828 6.621094 12.5 6.875 12.5 L 13.125 12.5 C 13.378905 12.5 13.619791 12.548828 13.847656 12.646484 C 14.075521 12.744141 14.275716 12.879232 14.448242 13.051758 C 14.620768 13.224284 14.755858 13.424479 14.853516 13.652344 C 14.951171 13.880209 14.999999 14.121094 15 14.375 L 15 16.25 L 18.125 16.25 C 18.29427 16.25 18.440754 16.188152 18.564453 16.064453 C 18.68815 15.940756 18.75 15.794271 18.75 15.625 L 18.75 4.375 C 18.75 4.20573 18.68815 4.059246 18.564453 3.935547 C 18.440754 3.81185 18.29427 3.75 18.125 3.75 L 1.875 3.75 C 1.705729 3.75 1.559245 3.81185 1.435547 3.935547 C 1.311849 4.059246 1.25 4.20573 1.25 4.375 L 1.25 15.625 C 1.25 15.794271 1.311849 15.940756 1.435547 16.064453 C 1.559245 16.188152 1.705729 16.25 1.875 16.25 Z M 6.875 8.056641 C 6.875 7.633464 6.959635 7.236328 7.128906 6.865234 C 7.298177 6.494141 7.526041 6.170248 7.8125 5.893555 C 8.098958 5.616862 8.430989 5.398764 8.808594 5.239258 C 9.186197 5.079754 9.583333 5.000001 10 5 C 10.436197 5.000001 10.843099 5.081381 11.220703 5.244141 C 11.598307 5.406901 11.928711 5.629883 12.211914 5.913086 C 12.495117 6.196289 12.718099 6.526693 12.880859 6.904297 C 13.043619 7.281901 13.124999 7.688803 13.125 8.125 C 13.124999 8.561198 13.043619 8.9681 12.880859 9.345703 C 12.718099 9.723308 12.495117 10.053711 12.211914 10.336914 C 11.928711 10.620117 11.598307 10.8431 11.220703 11.005859 C 10.843099 11.16862 10.436197 11.25 10 11.25 C 9.550781 11.25 9.135742 11.166992 8.754883 11.000977 C 8.374023 10.834961 8.043619 10.607097 7.763672 10.317383 C 7.483724 10.02767 7.265625 9.689128 7.109375 9.301758 C 6.953125 8.914389 6.875 8.49935 6.875 8.056641 Z M 11.875 8.125 C 11.875 7.871094 11.826172 7.630209 11.728516 7.402344 C 11.630859 7.174479 11.495768 6.974284 11.323242 6.801758 C 11.150716 6.629232 10.950521 6.494141 10.722656 6.396484 C 10.494791 6.298828 10.253906 6.25 10 6.25 C 9.746094 6.25 9.505208 6.298828 9.277344 6.396484 C 9.049479 6.494141 8.849283 6.629232 8.676758 6.801758 C 8.504231 6.974284 8.369141 7.174479 8.271484 7.402344 C 8.173828 7.630209 8.125 7.871094 8.125 8.125 C 8.125 8.378906 8.173828 8.619792 8.271484 8.847656 C 8.369141 9.075521 8.504231 9.275717 8.676758 9.448242 C 8.849283 9.620769 9.049479 9.755859 9.277344 9.853516 C 9.505208 9.951172 9.746094 10 10 10 C 10.253906 10 10.494791 9.951172 10.722656 9.853516 C 10.950521 9.755859 11.150716 9.620769 11.323242 9.448242 C 11.495768 9.275717 11.630859 9.075521 11.728516 8.847656 C 11.826172 8.619792 11.875 8.378906 11.875 8.125 Z M 6.25 16.25 L 13.75 16.25 L 13.75 14.375 C 13.75 14.205729 13.68815 14.059245 13.564453 13.935547 C 13.440755 13.81185 13.294271 13.75 13.125 13.75 L 6.875 13.75 C 6.705729 13.75 6.559244 13.81185 6.435547 13.935547 C 6.311849 14.059245 6.25 14.205729 6.25 14.375 Z " />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
<VisualState x:Name="Completed">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="StatusText.Text" Value="{x:Bind domain:Translator.AccountCreationDialog_Completed}" />
|
||||
|
||||
40
Wino.Mail/Dialogs/CreateAccountAliasDialog.xaml
Normal file
40
Wino.Mail/Dialogs/CreateAccountAliasDialog.xaml
Normal file
@@ -0,0 +1,40 @@
|
||||
<ContentDialog
|
||||
x:Class="Wino.Dialogs.CreateAccountAliasDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Wino.Dialogs"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
PrimaryButtonText="{x:Bind domain:Translator.Buttons_Create}"
|
||||
SecondaryButtonText="{x:Bind domain:Translator.Buttons_Cancel}"
|
||||
DefaultButton="Primary"
|
||||
PrimaryButtonClick="CreateClicked"
|
||||
Title="{x:Bind domain:Translator.CreateAccountAliasDialog_Title}"
|
||||
Style="{StaticResource WinoDialogStyle}">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Text="{x:Bind domain:Translator.CreateAccountAliasDialog_Description}" Style="{StaticResource CaptionTextBlockStyle}" />
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Margin="0,20"
|
||||
Spacing="8">
|
||||
<TextBox
|
||||
x:Name="AliasTextBox"
|
||||
PlaceholderText="{x:Bind domain:Translator.CreateAccountAliasDialog_AliasAddressPlaceholder}"
|
||||
Header="{x:Bind domain:Translator.CreateAccountAliasDialog_AliasAddress}" />
|
||||
|
||||
<TextBox
|
||||
x:Name="ReplyToTextBox"
|
||||
PlaceholderText="{x:Bind domain:Translator.CreateAccountAliasDialog_ReplyToAddressPlaceholder}"
|
||||
Header="{x:Bind domain:Translator.CreateAccountAliasDialog_ReplyToAddress}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
|
||||
30
Wino.Mail/Dialogs/CreateAccountAliasDialog.xaml.cs
Normal file
30
Wino.Mail/Dialogs/CreateAccountAliasDialog.xaml.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Dialogs
|
||||
{
|
||||
public sealed partial class CreateAccountAliasDialog : ContentDialog, ICreateAccountAliasDialog
|
||||
{
|
||||
public MailAccountAlias CreatedAccountAlias { get; set; }
|
||||
public CreateAccountAliasDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void CreateClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
CreatedAccountAlias = new MailAccountAlias
|
||||
{
|
||||
AliasAddress = AliasTextBox.Text.Trim(),
|
||||
ReplyToAddress = ReplyToTextBox.Text.Trim(),
|
||||
Id = Guid.NewGuid(),
|
||||
IsPrimary = false,
|
||||
IsVerified = false
|
||||
};
|
||||
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,14 +56,6 @@
|
||||
PlaceholderText="{x:Bind domain:Translator.NewAccountDialog_AccountNamePlaceholder}"
|
||||
TextChanged="AccountNameChanged" />
|
||||
|
||||
<!-- Sender Name -->
|
||||
<TextBox
|
||||
x:Name="SenderNameTextbox"
|
||||
Grid.Row="1"
|
||||
Header="{x:Bind domain:Translator.AccountSettingsDialog_AccountName}"
|
||||
PlaceholderText="{x:Bind domain:Translator.AccountSettingsDialog_AccountNamePlaceholder}"
|
||||
TextChanged="SenderNameChanged" />
|
||||
|
||||
<!--
|
||||
TODO: Move Name, Sender Name and Color Picker to another Frame.
|
||||
Provider selection should be first, then account details.
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Wino.Dialogs
|
||||
|
||||
if (IsSecondaryButtonEnabled)
|
||||
{
|
||||
Result = new AccountCreationDialogResult(SelectedMailProvider.Type, AccountNameTextbox.Text.Trim(), SenderNameTextbox.Text.Trim());
|
||||
Result = new AccountCreationDialogResult(SelectedMailProvider.Type, AccountNameTextbox.Text.Trim());
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
@@ -68,8 +68,7 @@ namespace Wino.Dialogs
|
||||
{
|
||||
bool shouldEnable = SelectedMailProvider != null
|
||||
&& SelectedMailProvider.IsSupported
|
||||
&& !string.IsNullOrEmpty(AccountNameTextbox.Text)
|
||||
&& (SelectedMailProvider.RequireSenderNameOnCreationDialog ? !string.IsNullOrEmpty(SenderNameTextbox.Text) : true);
|
||||
&& !string.IsNullOrEmpty(AccountNameTextbox.Text);
|
||||
|
||||
IsPrimaryButtonEnabled = shouldEnable;
|
||||
}
|
||||
@@ -77,7 +76,6 @@ namespace Wino.Dialogs
|
||||
private void ValidateNames()
|
||||
{
|
||||
AccountNameTextbox.IsEnabled = SelectedMailProvider != null;
|
||||
SenderNameTextbox.IsEnabled = SelectedMailProvider != null && SelectedMailProvider.Type != Core.Domain.Enums.MailProviderType.IMAP4;
|
||||
}
|
||||
|
||||
private void DialogOpened(ContentDialog sender, ContentDialogOpenedEventArgs args) => Validate();
|
||||
|
||||
@@ -216,6 +216,18 @@ namespace Wino.Services
|
||||
return storeDialog;
|
||||
}
|
||||
|
||||
public async Task<ICreateAccountAliasDialog> ShowCreateAccountAliasDialogAsync()
|
||||
{
|
||||
var createAccountAliasDialog = new CreateAccountAliasDialog()
|
||||
{
|
||||
RequestedTheme = _themeService.RootTheme.ToWindowsElementTheme()
|
||||
};
|
||||
|
||||
await HandleDialogPresentationAsync(createAccountAliasDialog);
|
||||
|
||||
return createAccountAliasDialog;
|
||||
}
|
||||
|
||||
public async Task HandleSystemFolderConfigurationDialogAsync(Guid accountId, IFolderService folderService)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -88,6 +88,8 @@ namespace Wino.Services
|
||||
return typeof(SettingOptionsPage);
|
||||
case WinoPage.AppPreferencesPage:
|
||||
return typeof(AppPreferencesPage);
|
||||
case WinoPage.AliasManagementPage:
|
||||
return typeof(AliasManagementPage);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
|
||||
<SolidColorBrush x:Key="FolderSyncBrush">#1abc9c</SolidColorBrush>
|
||||
|
||||
<SolidColorBrush x:Key="AliasUnverifiedBrush">#ff7675</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="AliasVerifiedBrush">#1abc9c</SolidColorBrush>
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Name="Light">
|
||||
<SolidColorBrush x:Key="AttachmentBrush">#fdcb6e</SolidColorBrush>
|
||||
|
||||
6
Wino.Mail/Views/Abstract/AliasManagementPageAbstract.cs
Normal file
6
Wino.Mail/Views/Abstract/AliasManagementPageAbstract.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
using Wino.Mail.ViewModels;
|
||||
|
||||
namespace Wino.Views.Abstract
|
||||
{
|
||||
public abstract class AliasManagementPageAbstract : BasePage<AliasManagementPageViewModel> { }
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -443,7 +443,6 @@
|
||||
x:Name="AccountsComboBox"
|
||||
Grid.Column="1"
|
||||
IsEditable="False"
|
||||
IsEnabled="False"
|
||||
ItemsSource="{x:Bind ViewModel.Accounts}"
|
||||
SelectedItem="{x:Bind ViewModel.ComposingAccount, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace Wino.Views
|
||||
WinoPage.SignatureManagementPage => typeof(SignatureManagementPage),
|
||||
WinoPage.AccountDetailsPage => typeof(AccountDetailsPage),
|
||||
WinoPage.MergedAccountDetailsPage => typeof(MergedAccountDetailsPage),
|
||||
WinoPage.AliasManagementPage => typeof(AliasManagementPage),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
163
Wino.Mail/Views/Settings/AliasManagementPage.xaml
Normal file
163
Wino.Mail/Views/Settings/AliasManagementPage.xaml
Normal file
File diff suppressed because one or more lines are too long
12
Wino.Mail/Views/Settings/AliasManagementPage.xaml.cs
Normal file
12
Wino.Mail/Views/Settings/AliasManagementPage.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Wino.Views.Abstract;
|
||||
|
||||
namespace Wino.Views.Settings
|
||||
{
|
||||
public sealed partial class AliasManagementPage : AliasManagementPageAbstract
|
||||
{
|
||||
public AliasManagementPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,6 +169,9 @@
|
||||
<PackageReference Include="CommunityToolkit.Uwp.Animations">
|
||||
<Version>8.0.240109</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="CommunityToolkit.Uwp.Behaviors">
|
||||
<Version>8.0.240109</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="CommunityToolkit.Uwp.Controls.SettingsControls">
|
||||
<Version>8.0.240109</Version>
|
||||
</PackageReference>
|
||||
@@ -268,6 +271,9 @@
|
||||
<Compile Include="Dialogs\NewImapSetupDialog.xaml.cs">
|
||||
<DependentUpon>NewImapSetupDialog.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Dialogs\CreateAccountAliasDialog.xaml.cs">
|
||||
<DependentUpon>CreateAccountAliasDialog.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Dialogs\StoreRatingDialog.xaml.cs">
|
||||
<DependentUpon>StoreRatingDialog.xaml</DependentUpon>
|
||||
</Compile>
|
||||
@@ -340,6 +346,7 @@
|
||||
<Compile Include="Views\Abstract\AboutPageAbstract.cs" />
|
||||
<Compile Include="Views\Abstract\AccountDetailsPageAbstract.cs" />
|
||||
<Compile Include="Views\Abstract\AccountManagementPageAbstract.cs" />
|
||||
<Compile Include="Views\Abstract\AliasManagementPageAbstract.cs" />
|
||||
<Compile Include="Views\Abstract\AppPreferencesPageAbstract.cs" />
|
||||
<Compile Include="Views\Abstract\AppShellAbstract.cs" />
|
||||
<Compile Include="Views\Abstract\ComposePageAbstract.cs" />
|
||||
@@ -423,6 +430,9 @@
|
||||
<Compile Include="Views\Settings\SettingOptionsPage.xaml.cs">
|
||||
<DependentUpon>SettingOptionsPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\Settings\AliasManagementPage.xaml.cs">
|
||||
<DependentUpon>AliasManagementPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\Settings\SignatureManagementPage.xaml.cs">
|
||||
<DependentUpon>SignatureManagementPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
@@ -509,6 +519,10 @@
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Dialogs\CreateAccountAliasDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Dialogs\StoreRatingDialog.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
@@ -657,6 +671,10 @@
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\Settings\AliasManagementPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Views\Settings\SignatureManagementPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Wino.Server.MessageHandlers
|
||||
{
|
||||
var synchronizationResult = await synchronizer.SynchronizeAsync(message.Options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (synchronizationResult.DownloadedMessages.Any())
|
||||
if (synchronizationResult.DownloadedMessages?.Any() ?? false || !synchronizer.Account.Preferences.IsNotificationsEnabled)
|
||||
{
|
||||
var accountInboxFolder = await _folderService.GetSpecialFolderByAccountIdAsync(message.Options.AccountId, SpecialFolderType.Inbox);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user