Fix merge conflicts
This commit is contained in:
@@ -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,83 @@ namespace Wino.Mail.ViewModels
|
||||
await _accountService.CreateAccountAsync(createdAccount, tokenInformation, customServerInformation);
|
||||
|
||||
// Local account has been created.
|
||||
// Create new synchronizer and start synchronization.
|
||||
|
||||
// Sync profile information if supported.
|
||||
if (createdAccount.IsProfileInfoSyncSupported)
|
||||
{
|
||||
// Start profile information synchronization.
|
||||
// It's only available for Outlook and Gmail synchronizers.
|
||||
|
||||
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);
|
||||
// Sync aliases if supported.
|
||||
if (createdAccount.IsAliasSyncSupported)
|
||||
{
|
||||
// Try to synchronize aliases for the account.
|
||||
|
||||
if (!isInboxAvailable)
|
||||
throw new Exception(Translator.Exception_InboxNotAvailable);
|
||||
var aliasSyncOptions = new SynchronizationOptions()
|
||||
{
|
||||
AccountId = createdAccount.Id,
|
||||
Type = SynchronizationType.Alias
|
||||
};
|
||||
|
||||
var aliasSyncResponse = await _winoServerConnectionManager.GetResponseAsync<SynchronizationResult, NewSynchronizationRequested>(new NewSynchronizationRequested(aliasSyncOptions, SynchronizationSource.Client));
|
||||
var aliasSynchronizationResult = folderSynchronizationResponse.Data;
|
||||
|
||||
if (aliasSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
|
||||
throw new Exception(Translator.Exception_FailedToSynchronizeAliases);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create root primary alias for the account.
|
||||
// This is only available for accounts that do not support alias synchronization.
|
||||
|
||||
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));
|
||||
|
||||
143
Wino.Mail.ViewModels/AliasManagementPageViewModel.cs
Normal file
143
Wino.Mail.ViewModels/AliasManagementPageViewModel.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
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;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Messaging.Server;
|
||||
|
||||
namespace Wino.Mail.ViewModels
|
||||
{
|
||||
public partial class AliasManagementPageViewModel : BaseViewModel
|
||||
{
|
||||
private readonly IAccountService _accountService;
|
||||
private readonly IWinoServerConnectionManager _winoServerConnectionManager;
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(CanSynchronizeAliases))]
|
||||
private MailAccount account;
|
||||
|
||||
[ObservableProperty]
|
||||
private List<MailAccountAlias> accountAliases = [];
|
||||
|
||||
public bool CanSynchronizeAliases => Account?.IsAliasSyncSupported ?? false;
|
||||
|
||||
public AliasManagementPageViewModel(IDialogService dialogService,
|
||||
IAccountService accountService,
|
||||
IWinoServerConnectionManager winoServerConnectionManager) : base(dialogService)
|
||||
{
|
||||
_accountService = accountService;
|
||||
_winoServerConnectionManager = winoServerConnectionManager;
|
||||
}
|
||||
|
||||
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 SyncAliasesAsync()
|
||||
{
|
||||
if (!CanSynchronizeAliases) return;
|
||||
|
||||
var aliasSyncOptions = new SynchronizationOptions()
|
||||
{
|
||||
AccountId = Account.Id,
|
||||
Type = SynchronizationType.Alias
|
||||
};
|
||||
|
||||
var aliasSyncResponse = await _winoServerConnectionManager.GetResponseAsync<SynchronizationResult, NewSynchronizationRequested>(new NewSynchronizationRequested(aliasSyncOptions, SynchronizationSource.Client));
|
||||
|
||||
if (aliasSyncResponse.IsSuccess)
|
||||
await LoadAliasesAsync();
|
||||
else
|
||||
DialogService.InfoBarMessage(Translator.GeneralTitle_Error, aliasSyncResponse.Message, InfoBarMessageType.Error);
|
||||
}
|
||||
|
||||
[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.AliasAddress) || (!string.IsNullOrEmpty(newAlias.ReplyToAddress) && !EmailValidator.Validate(newAlias.ReplyToAddress)))
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -729,17 +729,26 @@ namespace Wino.Mail.ViewModels
|
||||
operationAccount = accounts.FirstOrDefault();
|
||||
else
|
||||
{
|
||||
// There are multiple accounts and there is no selection.
|
||||
// Don't list all accounts, but only accounts that belong to Merged Inbox.
|
||||
|
||||
if (latestSelectedAccountMenuItem is MergedAccountMenuItem selectedMergedAccountMenuItem)
|
||||
{
|
||||
// There are multiple accounts and there is no selection.
|
||||
// Don't list all accounts, but only accounts that belong to Merged Inbox.
|
||||
|
||||
var mergedAccounts = accounts.Where(a => a.MergedInboxId == selectedMergedAccountMenuItem.EntityId);
|
||||
|
||||
if (!mergedAccounts.Any()) return;
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,7 +788,7 @@ namespace Wino.Mail.ViewModels
|
||||
|
||||
var (draftMailCopy, draftBase64MimeMessage) = await _mailService.CreateDraftAsync(account.Id, draftOptions).ConfigureAwait(false);
|
||||
|
||||
var draftPreparationRequest = new DraftPreparationRequest(account, draftMailCopy, draftBase64MimeMessage);
|
||||
var draftPreparationRequest = new DraftPreparationRequest(account, draftMailCopy, draftBase64MimeMessage, draftOptions.Reason);
|
||||
await _winoRequestDelegator.ExecuteAsync(draftPreparationRequest);
|
||||
}
|
||||
|
||||
@@ -795,7 +804,7 @@ namespace Wino.Mail.ViewModels
|
||||
}
|
||||
|
||||
protected override void OnAccountRemoved(MailAccount removedAccount)
|
||||
=> Messenger.Send(new AccountsMenuRefreshRequested(true));
|
||||
=> Messenger.Send(new AccountsMenuRefreshRequested(false));
|
||||
|
||||
protected override async void OnAccountCreated(MailAccount createdAccount)
|
||||
{
|
||||
@@ -877,12 +886,9 @@ namespace Wino.Mail.ViewModels
|
||||
{
|
||||
await RecreateMenuItemsAsync();
|
||||
|
||||
if (message.AutomaticallyNavigateFirstItem)
|
||||
if (MenuItems.FirstOrDefault(a => a is IAccountMenuItem) is IAccountMenuItem firstAccount)
|
||||
{
|
||||
if (MenuItems.FirstOrDefault(a => a is IAccountMenuItem) is IAccountMenuItem firstAccount)
|
||||
{
|
||||
await ChangeLoadedAccountAsync(firstAccount);
|
||||
}
|
||||
await ChangeLoadedAccountAsync(firstAccount, message.AutomaticallyNavigateFirstItem);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using MimeKit;
|
||||
using MimeKit.Utils;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
@@ -21,6 +20,7 @@ using Wino.Core.Extensions;
|
||||
using Wino.Core.Services;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
using Wino.Messaging.Client.Mails;
|
||||
using Wino.Messaging.Server;
|
||||
|
||||
namespace Wino.Mail.ViewModels
|
||||
{
|
||||
@@ -68,6 +68,12 @@ namespace Wino.Mail.ViewModels
|
||||
[NotifyCanExecuteChangedFor(nameof(SendCommand))]
|
||||
private MailAccount composingAccount;
|
||||
|
||||
[ObservableProperty]
|
||||
private List<MailAccountAlias> availableAliases;
|
||||
|
||||
[ObservableProperty]
|
||||
private MailAccountAlias selectedAlias;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool isDraggingOverComposerGrid;
|
||||
|
||||
@@ -112,6 +118,7 @@ namespace Wino.Mail.ViewModels
|
||||
private readonly IWinoRequestDelegator _worker;
|
||||
public readonly IFontService FontService;
|
||||
public readonly IPreferencesService PreferencesService;
|
||||
private readonly IWinoServerConnectionManager _winoServerConnectionManager;
|
||||
public readonly IContactService ContactService;
|
||||
|
||||
public ComposePageViewModel(IDialogService dialogService,
|
||||
@@ -124,21 +131,23 @@ namespace Wino.Mail.ViewModels
|
||||
IWinoRequestDelegator worker,
|
||||
IContactService contactService,
|
||||
IFontService fontService,
|
||||
IPreferencesService preferencesService) : base(dialogService)
|
||||
IPreferencesService preferencesService,
|
||||
IWinoServerConnectionManager winoServerConnectionManager) : base(dialogService)
|
||||
{
|
||||
NativeAppService = nativeAppService;
|
||||
_folderService = folderService;
|
||||
ContactService = contactService;
|
||||
FontService = fontService;
|
||||
PreferencesService = preferencesService;
|
||||
|
||||
_folderService = folderService;
|
||||
_mailService = mailService;
|
||||
_launchProtocolService = launchProtocolService;
|
||||
_mimeFileService = mimeFileService;
|
||||
_accountService = accountService;
|
||||
_worker = worker;
|
||||
_winoServerConnectionManager = winoServerConnectionManager;
|
||||
|
||||
SelectedToolbarSection = ToolbarSections[0];
|
||||
PreferencesService = preferencesService;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -163,6 +172,12 @@ namespace Wino.Mail.ViewModels
|
||||
if (!isConfirmed) return;
|
||||
}
|
||||
|
||||
if (SelectedAlias == null)
|
||||
{
|
||||
DialogService.InfoBarMessage(Translator.DialogMessage_AliasNotSelectedTitle, Translator.DialogMessage_AliasNotSelectedMessage, InfoBarMessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Save mime changes before sending.
|
||||
await UpdateMimeChangesAsync().ConfigureAwait(false);
|
||||
|
||||
@@ -177,11 +192,26 @@ namespace Wino.Mail.ViewModels
|
||||
int count = (int)memoryStream.Length;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public async Task IncludeAttachmentAsync(MailAttachmentViewModel viewModel)
|
||||
{
|
||||
//if (bodyBuilder == null) return;
|
||||
|
||||
//bodyBuilder.Attachments.Add(viewModel.FileName, new MemoryStream(viewModel.Content));
|
||||
|
||||
//LoadAttachments();
|
||||
IncludedAttachments.Add(viewModel);
|
||||
}
|
||||
|
||||
private async Task UpdateMimeChangesAsync()
|
||||
{
|
||||
if (isUpdatingMimeBlocked || CurrentMimeMessage == null || ComposingAccount == null || CurrentMailDraftItem == null) return;
|
||||
@@ -194,6 +224,8 @@ namespace Wino.Mail.ViewModels
|
||||
|
||||
SaveImportance();
|
||||
SaveSubject();
|
||||
SaveFromAddress();
|
||||
SaveReplyToAddress();
|
||||
|
||||
await SaveAttachmentsAsync();
|
||||
await SaveBodyAsync();
|
||||
@@ -207,6 +239,8 @@ namespace Wino.Mail.ViewModels
|
||||
{
|
||||
CurrentMailDraftItem.Subject = CurrentMimeMessage.Subject;
|
||||
CurrentMailDraftItem.PreviewText = CurrentMimeMessage.TextBody;
|
||||
CurrentMailDraftItem.FromAddress = SelectedAlias.AliasAddress;
|
||||
CurrentMailDraftItem.HasAttachments = CurrentMimeMessage.Attachments.Any();
|
||||
|
||||
// Update database.
|
||||
await _mailService.UpdateMailAsync(CurrentMailDraftItem.MailCopy);
|
||||
@@ -224,7 +258,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()
|
||||
{
|
||||
@@ -234,6 +271,31 @@ namespace Wino.Mail.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearCurrentMimeAttachments()
|
||||
{
|
||||
var attachments = new List<MimePart>();
|
||||
var multiparts = new List<Multipart>();
|
||||
var iter = new MimeIterator(CurrentMimeMessage);
|
||||
|
||||
// collect our list of attachments and their parent multiparts
|
||||
while (iter.MoveNext())
|
||||
{
|
||||
var multipart = iter.Parent as Multipart;
|
||||
var part = iter.Current as MimePart;
|
||||
|
||||
if (multipart != null && part != null && part.IsAttachment)
|
||||
{
|
||||
// keep track of each attachment's parent multipart
|
||||
multiparts.Add(multipart);
|
||||
attachments.Add(part);
|
||||
}
|
||||
}
|
||||
|
||||
// now remove each attachment from its parent multipart...
|
||||
for (int i = 0; i < attachments.Count; i++)
|
||||
multiparts[i].Remove(attachments[i]);
|
||||
}
|
||||
|
||||
private async Task SaveBodyAsync()
|
||||
{
|
||||
if (GetHTMLBodyFunction != null)
|
||||
@@ -241,8 +303,7 @@ namespace Wino.Mail.ViewModels
|
||||
bodyBuilder.SetHtmlBody(await GetHTMLBodyFunction());
|
||||
}
|
||||
|
||||
if (bodyBuilder.HtmlBody != null && bodyBuilder.TextBody != null)
|
||||
CurrentMimeMessage.Body = bodyBuilder.ToMessageBody();
|
||||
CurrentMimeMessage.Body = bodyBuilder.ToMessageBody();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(canSendMail))]
|
||||
@@ -280,6 +341,8 @@ namespace Wino.Mail.ViewModels
|
||||
base.OnNavigatedFrom(mode, parameters);
|
||||
|
||||
await UpdateMimeChangesAsync().ConfigureAwait(false);
|
||||
|
||||
Messenger.Send(new KillChromiumRequested());
|
||||
}
|
||||
|
||||
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
@@ -288,92 +351,58 @@ namespace Wino.Mail.ViewModels
|
||||
|
||||
if (parameters != null && parameters is MailItemViewModel mailItem)
|
||||
{
|
||||
await LoadAccountsAsync();
|
||||
|
||||
CurrentMailDraftItem = mailItem;
|
||||
|
||||
_ = TryPrepareComposeAsync(true);
|
||||
}
|
||||
|
||||
ToItems.CollectionChanged -= ContactListCollectionChanged;
|
||||
ToItems.CollectionChanged += ContactListCollectionChanged;
|
||||
|
||||
// Check if there is any delivering mail address from protocol launch.
|
||||
|
||||
if (_launchProtocolService.MailToUri != null)
|
||||
{
|
||||
// TODO
|
||||
//var requestedMailContact = await GetAddressInformationAsync(_launchProtocolService.MailtoParameters, ToItems);
|
||||
|
||||
//if (requestedMailContact != null)
|
||||
//{
|
||||
// ToItems.Add(requestedMailContact);
|
||||
//}
|
||||
//else
|
||||
// DialogService.InfoBarMessage("Invalid Address", "Address is not a valid e-mail address.", InfoBarMessageType.Warning);
|
||||
|
||||
// Clear the address.
|
||||
_launchProtocolService.MailToUri = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ContactListCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
// Prevent duplicates.
|
||||
if (!(sender is ObservableCollection<AddressInformation> list))
|
||||
return;
|
||||
|
||||
foreach (var item in e.NewItems)
|
||||
{
|
||||
if (item is AddressInformation addedInfo && list.Count(a => a == addedInfo) > 1)
|
||||
{
|
||||
var addedIndex = list.IndexOf(addedInfo);
|
||||
list.RemoveAt(addedIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadAccountsAsync()
|
||||
{
|
||||
// Load accounts
|
||||
|
||||
var accounts = await _accountService.GetAccountsAsync();
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
Accounts.Add(account);
|
||||
await TryPrepareComposeAsync(true);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> InitializeComposerAccountAsync()
|
||||
{
|
||||
if (CurrentMailDraftItem == null) return false;
|
||||
|
||||
if (ComposingAccount != null) return true;
|
||||
|
||||
if (CurrentMailDraftItem == null)
|
||||
return false;
|
||||
var composingAccount = await _accountService.GetAccountAsync(CurrentMailDraftItem.AssignedAccount.Id).ConfigureAwait(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(() =>
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (CurrentMailDraftItem == null)
|
||||
return;
|
||||
if (CurrentMailDraftItem == null) return;
|
||||
|
||||
bool isComposerInitialized = await InitializeComposerAccountAsync();
|
||||
|
||||
if (!isComposerInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!isComposerInitialized) return;
|
||||
|
||||
retry:
|
||||
|
||||
// Replying existing message.
|
||||
MimeMessageInformation mimeMessageInformation = null;
|
||||
@@ -386,18 +415,24 @@ namespace Wino.Mail.ViewModels
|
||||
{
|
||||
if (downloadIfNeeded)
|
||||
{
|
||||
// TODO: Folder id needs to be passed.
|
||||
// TODO: Send mail retrieve request.
|
||||
// _worker.Queue(new FetchSingleItemRequest(ComposingAccount.Id, CurrentMailDraftItem.Id, string.Empty));
|
||||
downloadIfNeeded = false;
|
||||
|
||||
var package = new DownloadMissingMessageRequested(CurrentMailDraftItem.AssignedAccount.Id, CurrentMailDraftItem.MailCopy);
|
||||
var downloadResponse = await _winoServerConnectionManager.GetResponseAsync<bool, DownloadMissingMessageRequested>(package);
|
||||
|
||||
if (downloadResponse.IsSuccess)
|
||||
{
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
//else
|
||||
// DialogService.ShowMIMENotFoundMessage();
|
||||
else
|
||||
DialogService.InfoBarMessage(Translator.Info_ComposerMissingMIMETitle, Translator.Info_ComposerMissingMIMEMessage, InfoBarMessageType.Error);
|
||||
|
||||
return;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
DialogService.InfoBarMessage("Busy", "Mail is being processed. Please wait a moment and try again.", InfoBarMessageType.Warning);
|
||||
DialogService.InfoBarMessage(Translator.Busy, Translator.Exception_MailProcessing, InfoBarMessageType.Warning);
|
||||
}
|
||||
catch (ComposerMimeNotFoundException)
|
||||
{
|
||||
@@ -416,6 +451,8 @@ namespace Wino.Mail.ViewModels
|
||||
{
|
||||
// Extract information
|
||||
|
||||
CurrentMimeMessage = replyingMime;
|
||||
|
||||
ToItems.Clear();
|
||||
CCItems.Clear();
|
||||
BCCItems.Clear();
|
||||
@@ -424,22 +461,22 @@ namespace Wino.Mail.ViewModels
|
||||
LoadAddressInfo(replyingMime.Cc, CCItems);
|
||||
LoadAddressInfo(replyingMime.Bcc, BCCItems);
|
||||
|
||||
LoadAttachments(replyingMime.Attachments);
|
||||
LoadAttachments();
|
||||
|
||||
if (replyingMime.Cc.Any() || replyingMime.Bcc.Any())
|
||||
IsCCBCCVisible = true;
|
||||
|
||||
Subject = replyingMime.Subject;
|
||||
|
||||
CurrentMimeMessage = replyingMime;
|
||||
|
||||
Messenger.Send(new CreateNewComposeMailRequested(renderModel));
|
||||
});
|
||||
}
|
||||
|
||||
private void LoadAttachments(IEnumerable<MimeEntity> mimeEntities)
|
||||
private void LoadAttachments()
|
||||
{
|
||||
foreach (var attachment in mimeEntities)
|
||||
if (CurrentMimeMessage == null) return;
|
||||
|
||||
foreach (var attachment in CurrentMimeMessage.Attachments)
|
||||
{
|
||||
if (attachment.IsAttachment && attachment is MimePart attachmentPart)
|
||||
{
|
||||
@@ -459,6 +496,28 @@ namespace Wino.Mail.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveFromAddress()
|
||||
{
|
||||
if (SelectedAlias == null) return;
|
||||
|
||||
CurrentMimeMessage.From.Clear();
|
||||
CurrentMimeMessage.From.Add(new MailboxAddress(ComposingAccount.SenderName, SelectedAlias.AliasAddress));
|
||||
}
|
||||
|
||||
private void SaveReplyToAddress()
|
||||
{
|
||||
if (SelectedAlias == 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)
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
@@ -23,6 +23,8 @@ namespace Wino.Mail.ViewModels.Data
|
||||
|
||||
public int HoldingAccountCount => 1;
|
||||
|
||||
public bool HasProfilePicture => !string.IsNullOrEmpty(Account.Base64ProfilePictureData);
|
||||
|
||||
public AccountProviderDetailViewModel(IProviderDetail providerDetail, MailAccount account)
|
||||
{
|
||||
ProviderDetail = providerDetail;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,8 @@ using Wino.Core.Extensions;
|
||||
|
||||
namespace Wino.Mail.ViewModels.Data
|
||||
{
|
||||
public class MailAttachmentViewModel : ObservableObject
|
||||
public partial class MailAttachmentViewModel : ObservableObject
|
||||
{
|
||||
private bool isBusy;
|
||||
private readonly MimePart _mimePart;
|
||||
|
||||
public MailAttachmentType AttachmentType { get; }
|
||||
@@ -22,23 +21,21 @@ namespace Wino.Mail.ViewModels.Data
|
||||
/// <summary>
|
||||
/// Gets or sets whether attachment is busy with opening or saving etc.
|
||||
/// </summary>
|
||||
public bool IsBusy
|
||||
{
|
||||
get => isBusy;
|
||||
set => SetProperty(ref isBusy, value);
|
||||
}
|
||||
[ObservableProperty]
|
||||
private bool isBusy;
|
||||
|
||||
public MailAttachmentViewModel(MimePart mimePart)
|
||||
{
|
||||
_mimePart = mimePart;
|
||||
|
||||
var array = new byte[_mimePart.Content.Stream.Length];
|
||||
_mimePart.Content.Stream.Read(array, 0, (int)_mimePart.Content.Stream.Length);
|
||||
var memoryStream = new MemoryStream();
|
||||
|
||||
Content = array;
|
||||
using (memoryStream) mimePart.Content.DecodeTo(memoryStream);
|
||||
|
||||
Content = memoryStream.ToArray();
|
||||
|
||||
FileName = mimePart.FileName;
|
||||
ReadableSize = mimePart.Content.Stream.Length.GetBytesReadable();
|
||||
ReadableSize = ((long)Content.Length).GetBytesReadable();
|
||||
|
||||
var extension = Path.GetExtension(FileName);
|
||||
AttachmentType = GetAttachmentType(extension);
|
||||
|
||||
@@ -18,8 +18,6 @@ namespace Wino.Mail.ViewModels.Data
|
||||
public string MessageId => ((IMailItem)MailCopy).MessageId;
|
||||
public string FromName => ((IMailItem)MailCopy).FromName ?? FromAddress;
|
||||
public DateTime CreationDate => ((IMailItem)MailCopy).CreationDate;
|
||||
public string FromAddress => ((IMailItem)MailCopy).FromAddress;
|
||||
public bool HasAttachments => ((IMailItem)MailCopy).HasAttachments;
|
||||
public string References => ((IMailItem)MailCopy).References;
|
||||
public string InReplyTo => ((IMailItem)MailCopy).InReplyTo;
|
||||
|
||||
@@ -77,6 +75,18 @@ namespace Wino.Mail.ViewModels.Data
|
||||
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 bool HasAttachments
|
||||
{
|
||||
get => MailCopy.HasAttachments;
|
||||
set => SetProperty(MailCopy.HasAttachments, value, MailCopy, (u, n) => u.HasAttachments = n);
|
||||
}
|
||||
|
||||
public MailItemFolder AssignedFolder => ((IMailItem)MailCopy).AssignedFolder;
|
||||
|
||||
public MailAccount AssignedAccount => ((IMailItem)MailCopy).AssignedAccount;
|
||||
@@ -94,6 +104,8 @@ namespace Wino.Mail.ViewModels.Data
|
||||
OnPropertyChanged(nameof(DraftId));
|
||||
OnPropertyChanged(nameof(Subject));
|
||||
OnPropertyChanged(nameof(PreviewText));
|
||||
OnPropertyChanged(nameof(FromAddress));
|
||||
OnPropertyChanged(nameof(HasAttachments));
|
||||
}
|
||||
|
||||
public IEnumerable<Guid> GetContainingIds() => new[] { UniqueId };
|
||||
|
||||
@@ -273,7 +273,7 @@ namespace Wino.Mail.ViewModels
|
||||
|
||||
var (draftMailCopy, draftBase64MimeMessage) = await _mailService.CreateDraftAsync(initializedMailItemViewModel.AssignedAccount.Id, draftOptions).ConfigureAwait(false);
|
||||
|
||||
var draftPreparationRequest = new DraftPreparationRequest(initializedMailItemViewModel.AssignedAccount, draftMailCopy, draftBase64MimeMessage, initializedMailItemViewModel.MailCopy);
|
||||
var draftPreparationRequest = new DraftPreparationRequest(initializedMailItemViewModel.AssignedAccount, draftMailCopy, draftBase64MimeMessage, draftOptions.Reason, initializedMailItemViewModel.MailCopy);
|
||||
|
||||
await _requestDelegator.ExecuteAsync(draftPreparationRequest);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user