Ability to select alias in composer page.

This commit is contained in:
Burak Kaan Köse
2024-08-17 22:55:58 +02:00
parent 55fe791c2a
commit 91ed0bb8bd
12 changed files with 436 additions and 376 deletions

View File

@@ -141,7 +141,7 @@ namespace Wino.Core.Domain.Entities
/// </summary> /// </summary>
[Ignore] [Ignore]
public MailAccount AssignedAccount { get; set; } public MailAccount AssignedAccount { get; set; }
public IEnumerable<Guid> GetContainingIds() => new[] { UniqueId }; public IEnumerable<Guid> GetContainingIds() => [UniqueId];
public override string ToString() => $"{Subject} <-> {Id}"; public override string ToString() => $"{Subject} <-> {Id}";
} }
} }

View File

@@ -0,0 +1,7 @@
namespace Wino.Core.Domain.Exceptions
{
public class MissingAliasException : System.Exception
{
public MissingAliasException() : base(Translator.Exception_MissingAlias) { }
}
}

View File

@@ -146,5 +146,14 @@ namespace Wino.Core.Domain.Interfaces
/// <param name="remoteAccountAliases">Remotely fetched basic alias info from synchronizer.</param> /// <param name="remoteAccountAliases">Remotely fetched basic alias info from synchronizer.</param>
/// <param name="account">Account to update remote aliases for..</param> /// <param name="account">Account to update remote aliases for..</param>
Task UpdateRemoteAliasInformationAsync(MailAccount account, List<RemoteAccountAlias> remoteAccountAliases); Task UpdateRemoteAliasInformationAsync(MailAccount account, List<RemoteAccountAlias> remoteAccountAliases);
/// <summary>
/// Gets the primary account alias for the given account id.
/// Used when creating draft messages.
/// </summary>
/// <param name="accountId">Account id.</param>
/// <returns>Primary alias for the account.</returns>
Task<MailAccountAlias> GetPrimaryAccountAliasAsync(Guid accountId);
} }
} }

View File

@@ -7,19 +7,22 @@ namespace Wino.Core.Domain.Models.MailItem
{ {
public class SendDraftPreparationRequest public class SendDraftPreparationRequest
{ {
public MailCopy MailItem { get; set; } public MailCopy MailItem { get; }
public string Base64MimeMessage { get; set; } public string Base64MimeMessage { get; }
public MailItemFolder SentFolder { get; set; } public MailItemFolder SentFolder { get; }
public MailItemFolder DraftFolder { get; set; } public MailItemFolder DraftFolder { get; }
public MailAccountPreferences AccountPreferences { get; set; } public MailAccountPreferences AccountPreferences { get; }
public MailAccountAlias SendingAlias { get; set; }
public SendDraftPreparationRequest(MailCopy mailItem, public SendDraftPreparationRequest(MailCopy mailItem,
MailAccountAlias sendingAlias,
MailItemFolder sentFolder, MailItemFolder sentFolder,
MailItemFolder draftFolder, MailItemFolder draftFolder,
MailAccountPreferences accountPreferences, MailAccountPreferences accountPreferences,
string base64MimeMessage) string base64MimeMessage)
{ {
MailItem = mailItem; MailItem = mailItem;
SendingAlias = sendingAlias;
SentFolder = sentFolder; SentFolder = sentFolder;
DraftFolder = draftFolder; DraftFolder = draftFolder;
AccountPreferences = accountPreferences; AccountPreferences = accountPreferences;

View File

@@ -67,6 +67,8 @@
"CustomThemeBuilder_WallpaperTitle": "Set custom wallpaper", "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_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_AccountLimitTitle": "Account Limit Reached",
"DialogMessage_AliasNotSelectedTitle": "Missing Alias",
"DialogMessage_AliasNotSelectedMessage": "You must select an alias before sending a message.",
"DialogMessage_AliasExistsTitle": "Existing Alias", "DialogMessage_AliasExistsTitle": "Existing Alias",
"DialogMessage_AliasExistsMessage": "This alias is already in use.", "DialogMessage_AliasExistsMessage": "This alias is already in use.",
"DialogMessage_InvalidAliasTitle": "Invalid Alias", "DialogMessage_InvalidAliasTitle": "Invalid Alias",
@@ -133,6 +135,7 @@
"Exception_CustomThemeMissingWallpaper": "You must provide a custom background image.", "Exception_CustomThemeMissingWallpaper": "You must provide a custom background image.",
"Exception_FailedToSynchronizeFolders": "Failed to synchronize folders", "Exception_FailedToSynchronizeFolders": "Failed to synchronize folders",
"Exception_FailedToSynchronizeAliases": "Failed to synchronize aliases", "Exception_FailedToSynchronizeAliases": "Failed to synchronize aliases",
"Exception_MissingAlias": "Primary alias does not exist for this account. Creating draft failed.",
"Exception_FailedToSynchronizeProfileInformation": "Failed to synchronize profile information", "Exception_FailedToSynchronizeProfileInformation": "Failed to synchronize profile information",
"Exception_GoogleAuthCallbackNull": "Callback uri is null on activation.", "Exception_GoogleAuthCallbackNull": "Callback uri is null on activation.",
"Exception_GoogleAuthCorruptedCode": "Corrupted authorization response.", "Exception_GoogleAuthCorruptedCode": "Corrupted authorization response.",

View File

@@ -358,6 +358,16 @@ namespace Wino.Core.Domain
/// </summary> /// </summary>
public static string DialogMessage_AccountLimitTitle => Resources.GetTranslatedString(@"DialogMessage_AccountLimitTitle"); public static string DialogMessage_AccountLimitTitle => Resources.GetTranslatedString(@"DialogMessage_AccountLimitTitle");
/// <summary>
/// Missing Alias
/// </summary>
public static string DialogMessage_AliasNotSelectedTitle => Resources.GetTranslatedString(@"DialogMessage_AliasNotSelectedTitle");
/// <summary>
/// You must select an alias before sending a message.
/// </summary>
public static string DialogMessage_AliasNotSelectedMessage => Resources.GetTranslatedString(@"DialogMessage_AliasNotSelectedMessage");
/// <summary> /// <summary>
/// Existing Alias /// Existing Alias
/// </summary> /// </summary>
@@ -688,6 +698,11 @@ namespace Wino.Core.Domain
/// </summary> /// </summary>
public static string Exception_FailedToSynchronizeAliases => Resources.GetTranslatedString(@"Exception_FailedToSynchronizeAliases"); public static string Exception_FailedToSynchronizeAliases => Resources.GetTranslatedString(@"Exception_FailedToSynchronizeAliases");
/// <summary>
/// Primary alias does not exist for this account. Creating draft failed.
/// </summary>
public static string Exception_MissingAlias => Resources.GetTranslatedString(@"Exception_MissingAlias");
/// <summary> /// <summary>
/// Failed to synchronize profile information /// Failed to synchronize profile information
/// </summary> /// </summary>

View File

@@ -559,5 +559,14 @@ namespace Wino.Core.Services
Messenger.Send(new AccountMenuItemsReordered(accountIdOrderPair)); Messenger.Send(new AccountMenuItemsReordered(accountIdOrderPair));
} }
public async Task<MailAccountAlias> GetPrimaryAccountAliasAsync(Guid accountId)
{
var aliases = await GetAccountAliasesAsync(accountId);
if (aliases == null || aliases.Count == 0) return null;
return aliases.FirstOrDefault(a => a.IsPrimary) ?? aliases.First();
}
} }
} }

View File

@@ -10,6 +10,7 @@ using SqlKata;
using Wino.Core.Domain; using Wino.Core.Domain;
using Wino.Core.Domain.Entities; using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Extensions; using Wino.Core.Domain.Extensions;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Comparers; using Wino.Core.Domain.Models.Comparers;
@@ -62,12 +63,14 @@ namespace Wino.Core.Services
// This header will be used to map the local draft copy with the remote draft copy. // This header will be used to map the local draft copy with the remote draft copy.
var mimeUniqueId = createdDraftMimeMessage.Headers[Constants.WinoLocalDraftHeader]; var mimeUniqueId = createdDraftMimeMessage.Headers[Constants.WinoLocalDraftHeader];
var primaryAlias = await _accountService.GetPrimaryAccountAliasAsync(accountId).ConfigureAwait(false);
var copy = new MailCopy var copy = new MailCopy
{ {
UniqueId = Guid.Parse(mimeUniqueId), UniqueId = Guid.Parse(mimeUniqueId),
Id = Guid.NewGuid().ToString(), // This will be replaced after network call with the remote draft id. Id = Guid.NewGuid().ToString(), // This will be replaced after network call with the remote draft id.
CreationDate = DateTime.UtcNow, CreationDate = DateTime.UtcNow,
FromAddress = composerAccount.Address, FromAddress = primaryAlias?.AliasAddress ?? composerAccount.Address,
FromName = composerAccount.SenderName, FromName = composerAccount.SenderName,
HasAttachments = false, HasAttachments = false,
Importance = MailImportance.Normal, Importance = MailImportance.Normal,
@@ -621,12 +624,17 @@ namespace Wino.Core.Services
// This unique id is stored in mime headers for Wino to identify remote message with local copy. // This unique id is stored in mime headers for Wino to identify remote message with local copy.
// Same unique id will be used for the local copy as well. // Same unique id will be used for the local copy as well.
// Synchronizer will map this unique id to the local draft copy after synchronization. // Synchronizer will map this unique id to the local draft copy after synchronization.
var message = new MimeMessage() var message = new MimeMessage()
{ {
Headers = { { Constants.WinoLocalDraftHeader, Guid.NewGuid().ToString() } }, Headers = { { Constants.WinoLocalDraftHeader, Guid.NewGuid().ToString() } },
From = { new MailboxAddress(account.SenderName, account.Address) }
}; };
var primaryAlias = await _accountService.GetPrimaryAccountAliasAsync(account.Id) ?? throw new MissingAliasException();
// Set FromName and FromAddress by alias.
message.From.Add(new MailboxAddress(account.SenderName, primaryAlias.AliasAddress));
var builder = new BodyBuilder(); var builder = new BodyBuilder();
var signature = await GetSignature(account, draftCreationOptions.Reason); var signature = await GetSignature(account, draftCreationOptions.Reason);

View File

@@ -728,18 +728,27 @@ namespace Wino.Mail.ViewModels
if (accounts.Count() == 1) if (accounts.Count() == 1)
operationAccount = accounts.FirstOrDefault(); operationAccount = accounts.FirstOrDefault();
else else
{
if (latestSelectedAccountMenuItem is MergedAccountMenuItem selectedMergedAccountMenuItem)
{ {
// There are multiple accounts and there is no selection. // There are multiple accounts and there is no selection.
// Don't list all accounts, but only accounts that belong to Merged Inbox. // Don't list all accounts, but only accounts that belong to Merged Inbox.
if (latestSelectedAccountMenuItem is MergedAccountMenuItem selectedMergedAccountMenuItem)
{
var mergedAccounts = accounts.Where(a => a.MergedInboxId == selectedMergedAccountMenuItem.EntityId); var mergedAccounts = accounts.Where(a => a.MergedInboxId == selectedMergedAccountMenuItem.EntityId);
if (!mergedAccounts.Any()) return; if (!mergedAccounts.Any()) return;
Messenger.Send(new CreateNewMailWithMultipleAccountsRequested(mergedAccounts.ToList())); Messenger.Send(new CreateNewMailWithMultipleAccountsRequested(mergedAccounts.ToList()));
} }
else if (latestSelectedAccountMenuItem is AccountMenuItem selectedAccountMenuItem)
{
operationAccount = selectedAccountMenuItem.HoldingAccounts.ElementAt(0);
}
else
{
// User is at some other page. List all accounts.
Messenger.Send(new CreateNewMailWithMultipleAccountsRequested(accounts));
}
} }
} }

View File

@@ -68,6 +68,12 @@ namespace Wino.Mail.ViewModels
[NotifyCanExecuteChangedFor(nameof(SendCommand))] [NotifyCanExecuteChangedFor(nameof(SendCommand))]
private MailAccount composingAccount; private MailAccount composingAccount;
[ObservableProperty]
private List<MailAccountAlias> availableAliases;
[ObservableProperty]
private MailAccountAlias selectedAlias;
[ObservableProperty] [ObservableProperty]
private bool isDraggingOverComposerGrid; private bool isDraggingOverComposerGrid;
@@ -166,6 +172,12 @@ namespace Wino.Mail.ViewModels
if (!isConfirmed) return; if (!isConfirmed) return;
} }
if (SelectedAlias == null)
{
DialogService.InfoBarMessage(Translator.DialogMessage_AliasNotSelectedTitle, Translator.DialogMessage_AliasNotSelectedMessage, InfoBarMessageType.Error);
return;
}
// Save mime changes before sending. // Save mime changes before sending.
await UpdateMimeChangesAsync().ConfigureAwait(false); await UpdateMimeChangesAsync().ConfigureAwait(false);
@@ -180,7 +192,12 @@ namespace Wino.Mail.ViewModels
int count = (int)memoryStream.Length; int count = (int)memoryStream.Length;
var base64EncodedMessage = Convert.ToBase64String(buffer); var base64EncodedMessage = Convert.ToBase64String(buffer);
var draftSendPreparationRequest = new SendDraftPreparationRequest(CurrentMailDraftItem.MailCopy, sentFolder, CurrentMailDraftItem.AssignedFolder, CurrentMailDraftItem.AssignedAccount.Preferences, base64EncodedMessage); var draftSendPreparationRequest = new SendDraftPreparationRequest(CurrentMailDraftItem.MailCopy,
SelectedAlias,
sentFolder,
CurrentMailDraftItem.AssignedFolder,
CurrentMailDraftItem.AssignedAccount.Preferences,
base64EncodedMessage);
await _worker.ExecuteAsync(draftSendPreparationRequest); await _worker.ExecuteAsync(draftSendPreparationRequest);
} }
@@ -197,6 +214,8 @@ namespace Wino.Mail.ViewModels
SaveImportance(); SaveImportance();
SaveSubject(); SaveSubject();
SaveFromAddress();
SaveReplyToAddress();
await SaveAttachmentsAsync(); await SaveAttachmentsAsync();
await SaveBodyAsync(); await SaveBodyAsync();
@@ -210,6 +229,7 @@ namespace Wino.Mail.ViewModels
{ {
CurrentMailDraftItem.Subject = CurrentMimeMessage.Subject; CurrentMailDraftItem.Subject = CurrentMimeMessage.Subject;
CurrentMailDraftItem.PreviewText = CurrentMimeMessage.TextBody; CurrentMailDraftItem.PreviewText = CurrentMimeMessage.TextBody;
CurrentMailDraftItem.FromAddress = SelectedAlias.AliasAddress;
// Update database. // Update database.
await _mailService.UpdateMailAsync(CurrentMailDraftItem.MailCopy); await _mailService.UpdateMailAsync(CurrentMailDraftItem.MailCopy);
@@ -227,7 +247,10 @@ namespace Wino.Mail.ViewModels
} }
} }
private void SaveImportance() { CurrentMimeMessage.Importance = IsImportanceSelected ? SelectedMessageImportance : MessageImportance.Normal; } private void SaveImportance()
{
CurrentMimeMessage.Importance = IsImportanceSelected ? SelectedMessageImportance : MessageImportance.Normal;
}
private void SaveSubject() private void SaveSubject()
{ {
@@ -285,14 +308,12 @@ namespace Wino.Mail.ViewModels
await UpdateMimeChangesAsync().ConfigureAwait(false); await UpdateMimeChangesAsync().ConfigureAwait(false);
} }
public override async void OnNavigatedTo(NavigationMode mode, object parameters) public override void OnNavigatedTo(NavigationMode mode, object parameters)
{ {
base.OnNavigatedTo(mode, parameters); base.OnNavigatedTo(mode, parameters);
if (parameters != null && parameters is MailItemViewModel mailItem) if (parameters != null && parameters is MailItemViewModel mailItem)
{ {
await LoadAccountsAsync();
CurrentMailDraftItem = mailItem; CurrentMailDraftItem = mailItem;
_ = TryPrepareComposeAsync(true); _ = TryPrepareComposeAsync(true);
@@ -321,44 +342,50 @@ namespace Wino.Mail.ViewModels
} }
} }
private async Task LoadAccountsAsync()
{
// Load accounts
var accounts = await _accountService.GetAccountsAsync();
foreach (var account in accounts)
{
Accounts.Add(account);
}
}
private async Task<bool> InitializeComposerAccountAsync() private async Task<bool> InitializeComposerAccountAsync()
{ {
if (CurrentMailDraftItem == null) return false;
if (ComposingAccount != null) return true; if (ComposingAccount != null) return true;
if (CurrentMailDraftItem == null) var composingAccount = await _accountService.GetAccountAsync(CurrentMailDraftItem.AssignedAccount.Id).ConfigureAwait(false);
return false; if (composingAccount == null) return false;
var aliases = await _accountService.GetAccountAliasesAsync(composingAccount.Id).ConfigureAwait(false);
if (aliases == null || !aliases.Any()) return false;
// MailAccountAlias primaryAlias = aliases.Find(a => a.IsPrimary) ?? aliases.First();
// Auto-select the correct alias from the message itself.
// If can't, fallback to primary alias.
MailAccountAlias primaryAlias = null;
if (!string.IsNullOrEmpty(CurrentMailDraftItem.FromAddress))
{
primaryAlias = aliases.Find(a => a.AliasAddress == CurrentMailDraftItem.FromAddress);
}
primaryAlias ??= await _accountService.GetPrimaryAccountAliasAsync(ComposingAccount.Id).ConfigureAwait(false);
await ExecuteUIThread(() => await ExecuteUIThread(() =>
{ {
ComposingAccount = Accounts.FirstOrDefault(a => a.Id == CurrentMailDraftItem.AssignedAccount.Id); ComposingAccount = composingAccount;
AvailableAliases = aliases;
SelectedAlias = primaryAlias;
}); });
return ComposingAccount != null; return true;
} }
private async Task TryPrepareComposeAsync(bool downloadIfNeeded) private async Task TryPrepareComposeAsync(bool downloadIfNeeded)
{ {
if (CurrentMailDraftItem == null) if (CurrentMailDraftItem == null) return;
return;
bool isComposerInitialized = await InitializeComposerAccountAsync(); bool isComposerInitialized = await InitializeComposerAccountAsync();
if (!isComposerInitialized) if (!isComposerInitialized) return;
{
return;
}
retry: retry:
@@ -452,6 +479,31 @@ namespace Wino.Mail.ViewModels
} }
} }
private void SaveFromAddress()
{
if (SelectedAlias == null || CurrentMimeMessage == null) return;
CurrentMimeMessage.From.Clear();
CurrentMimeMessage.From.Add(new MailboxAddress(ComposingAccount.SenderName, SelectedAlias.AliasAddress));
}
private void SaveReplyToAddress()
{
if (SelectedAlias == null || CurrentMimeMessage == null) return;
if (!string.IsNullOrEmpty(SelectedAlias.ReplyToAddress))
{
if (!CurrentMimeMessage.ReplyTo.Any(a => a is MailboxAddress mailboxAddress && mailboxAddress.Address == SelectedAlias.ReplyToAddress))
{
CurrentMimeMessage.ReplyTo.Clear();
CurrentMimeMessage.ReplyTo.Add(new MailboxAddress(SelectedAlias.ReplyToAddress, SelectedAlias.ReplyToAddress));
}
}
}
private void SaveAddressInfo(IEnumerable<AddressInformation> addresses, InternetAddressList list) private void SaveAddressInfo(IEnumerable<AddressInformation> addresses, InternetAddressList list)
{ {
list.Clear(); list.Clear();

View File

@@ -18,7 +18,6 @@ namespace Wino.Mail.ViewModels.Data
public string MessageId => ((IMailItem)MailCopy).MessageId; public string MessageId => ((IMailItem)MailCopy).MessageId;
public string FromName => ((IMailItem)MailCopy).FromName ?? FromAddress; public string FromName => ((IMailItem)MailCopy).FromName ?? FromAddress;
public DateTime CreationDate => ((IMailItem)MailCopy).CreationDate; public DateTime CreationDate => ((IMailItem)MailCopy).CreationDate;
public string FromAddress => ((IMailItem)MailCopy).FromAddress;
public bool HasAttachments => ((IMailItem)MailCopy).HasAttachments; public bool HasAttachments => ((IMailItem)MailCopy).HasAttachments;
public string References => ((IMailItem)MailCopy).References; public string References => ((IMailItem)MailCopy).References;
public string InReplyTo => ((IMailItem)MailCopy).InReplyTo; public string InReplyTo => ((IMailItem)MailCopy).InReplyTo;
@@ -77,6 +76,12 @@ namespace Wino.Mail.ViewModels.Data
set => SetProperty(MailCopy.PreviewText, value, MailCopy, (u, n) => u.PreviewText = n); set => SetProperty(MailCopy.PreviewText, value, MailCopy, (u, n) => u.PreviewText = n);
} }
public string FromAddress
{
get => MailCopy.FromAddress;
set => SetProperty(MailCopy.FromAddress, value, MailCopy, (u, n) => u.FromAddress = n);
}
public MailItemFolder AssignedFolder => ((IMailItem)MailCopy).AssignedFolder; public MailItemFolder AssignedFolder => ((IMailItem)MailCopy).AssignedFolder;
public MailAccount AssignedAccount => ((IMailItem)MailCopy).AssignedAccount; public MailAccount AssignedAccount => ((IMailItem)MailCopy).AssignedAccount;

File diff suppressed because one or more lines are too long