File scoped namespaces

This commit is contained in:
Aleh Khantsevich
2025-02-16 11:35:43 +01:00
committed by GitHub
parent c1336428dc
commit d31d8f574e
617 changed files with 32118 additions and 32737 deletions

View File

@@ -15,164 +15,163 @@ using Wino.Messaging.Client.Navigation;
using Wino.Messaging.Server;
using Wino.Messaging.UI;
namespace Wino.Mail.ViewModels
namespace Wino.Mail.ViewModels;
public partial class AccountDetailsPageViewModel : MailBaseViewModel
{
public partial class AccountDetailsPageViewModel : MailBaseViewModel
private readonly IMailDialogService _dialogService;
private readonly IAccountService _accountService;
private readonly IWinoServerConnectionManager _serverConnectionManager;
private readonly IFolderService _folderService;
public MailAccount Account { get; set; }
public ObservableCollection<IMailItemFolder> CurrentFolders { get; set; } = [];
[ObservableProperty]
private bool isFocusedInboxEnabled;
[ObservableProperty]
private bool areNotificationsEnabled;
[ObservableProperty]
private bool isSignatureEnabled;
[ObservableProperty]
private bool isAppendMessageSettingVisible;
[ObservableProperty]
private bool isAppendMessageSettinEnabled;
[ObservableProperty]
private bool isTaskbarBadgeEnabled;
public bool IsFocusedInboxSupportedForAccount => Account != null && Account.Preferences.IsFocusedInboxEnabled != null;
public AccountDetailsPageViewModel(IMailDialogService dialogService,
IAccountService accountService,
IWinoServerConnectionManager serverConnectionManager,
IFolderService folderService)
{
private readonly IMailDialogService _dialogService;
private readonly IAccountService _accountService;
private readonly IWinoServerConnectionManager _serverConnectionManager;
private readonly IFolderService _folderService;
_dialogService = dialogService;
_accountService = accountService;
_serverConnectionManager = serverConnectionManager;
_folderService = folderService;
}
public MailAccount Account { get; set; }
public ObservableCollection<IMailItemFolder> CurrentFolders { get; set; } = [];
[RelayCommand]
private Task SetupSpecialFolders()
=> _dialogService.HandleSystemFolderConfigurationDialogAsync(Account.Id, _folderService);
[ObservableProperty]
private bool isFocusedInboxEnabled;
[RelayCommand]
private void EditSignature()
=> Messenger.Send(new BreadcrumbNavigationRequested(Translator.SettingsSignature_Title, WinoPage.SignatureManagementPage, Account.Id));
[ObservableProperty]
private bool areNotificationsEnabled;
[RelayCommand]
private void EditAliases()
=> Messenger.Send(new BreadcrumbNavigationRequested(Translator.SettingsManageAliases_Title, WinoPage.AliasManagementPage, Account.Id));
[ObservableProperty]
private bool isSignatureEnabled;
public Task FolderSyncToggledAsync(IMailItemFolder folderStructure, bool isEnabled)
=> _folderService.ChangeFolderSynchronizationStateAsync(folderStructure.Id, isEnabled);
[ObservableProperty]
private bool isAppendMessageSettingVisible;
public Task FolderShowUnreadToggled(IMailItemFolder folderStructure, bool isEnabled)
=> _folderService.ChangeFolderShowUnreadCountStateAsync(folderStructure.Id, isEnabled);
[ObservableProperty]
private bool isAppendMessageSettinEnabled;
[RelayCommand]
private async Task RenameAccount()
{
if (Account == null)
return;
[ObservableProperty]
private bool isTaskbarBadgeEnabled;
var updatedAccount = await _dialogService.ShowEditAccountDialogAsync(Account);
public bool IsFocusedInboxSupportedForAccount => Account != null && Account.Preferences.IsFocusedInboxEnabled != null;
public AccountDetailsPageViewModel(IMailDialogService dialogService,
IAccountService accountService,
IWinoServerConnectionManager serverConnectionManager,
IFolderService folderService)
if (updatedAccount != null)
{
_dialogService = dialogService;
_accountService = accountService;
_serverConnectionManager = serverConnectionManager;
_folderService = folderService;
await _accountService.UpdateAccountAsync(updatedAccount);
ReportUIChange(new AccountUpdatedMessage(updatedAccount));
}
}
[RelayCommand]
private Task SetupSpecialFolders()
=> _dialogService.HandleSystemFolderConfigurationDialogAsync(Account.Id, _folderService);
[RelayCommand]
private async Task DeleteAccount()
{
if (Account == null)
return;
[RelayCommand]
private void EditSignature()
=> Messenger.Send(new BreadcrumbNavigationRequested(Translator.SettingsSignature_Title, WinoPage.SignatureManagementPage, Account.Id));
var confirmation = await _dialogService.ShowConfirmationDialogAsync(Translator.DialogMessage_DeleteAccountConfirmationTitle,
string.Format(Translator.DialogMessage_DeleteAccountConfirmationMessage, Account.Name),
Translator.Buttons_Delete);
[RelayCommand]
private void EditAliases()
=> Messenger.Send(new BreadcrumbNavigationRequested(Translator.SettingsManageAliases_Title, WinoPage.AliasManagementPage, Account.Id));
if (!confirmation)
return;
public Task FolderSyncToggledAsync(IMailItemFolder folderStructure, bool isEnabled)
=> _folderService.ChangeFolderSynchronizationStateAsync(folderStructure.Id, isEnabled);
public Task FolderShowUnreadToggled(IMailItemFolder folderStructure, bool isEnabled)
=> _folderService.ChangeFolderShowUnreadCountStateAsync(folderStructure.Id, isEnabled);
var isSynchronizerKilledResponse = await _serverConnectionManager.GetResponseAsync<bool, KillAccountSynchronizerRequested>(new KillAccountSynchronizerRequested(Account.Id));
[RelayCommand]
private async Task RenameAccount()
if (isSynchronizerKilledResponse.IsSuccess)
{
if (Account == null)
return;
await _accountService.DeleteAccountAsync(Account);
var updatedAccount = await _dialogService.ShowEditAccountDialogAsync(Account);
_dialogService.InfoBarMessage(Translator.Info_AccountDeletedTitle, string.Format(Translator.Info_AccountDeletedMessage, Account.Name), InfoBarMessageType.Success);
if (updatedAccount != null)
{
await _accountService.UpdateAccountAsync(updatedAccount);
ReportUIChange(new AccountUpdatedMessage(updatedAccount));
}
Messenger.Send(new BackBreadcrumNavigationRequested());
}
}
[RelayCommand]
private async Task DeleteAccount()
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
{
base.OnNavigatedTo(mode, parameters);
if (parameters is Guid accountId)
{
if (Account == null)
return;
Account = await _accountService.GetAccountAsync(accountId);
var confirmation = await _dialogService.ShowConfirmationDialogAsync(Translator.DialogMessage_DeleteAccountConfirmationTitle,
string.Format(Translator.DialogMessage_DeleteAccountConfirmationMessage, Account.Name),
Translator.Buttons_Delete);
IsFocusedInboxEnabled = Account.Preferences.IsFocusedInboxEnabled.GetValueOrDefault();
AreNotificationsEnabled = Account.Preferences.IsNotificationsEnabled;
IsSignatureEnabled = Account.Preferences.IsSignatureEnabled;
if (!confirmation)
return;
IsAppendMessageSettingVisible = Account.ProviderType == MailProviderType.IMAP4;
IsAppendMessageSettinEnabled = Account.Preferences.ShouldAppendMessagesToSentFolder;
IsTaskbarBadgeEnabled = Account.Preferences.IsTaskbarBadgeEnabled;
OnPropertyChanged(nameof(IsFocusedInboxSupportedForAccount));
var isSynchronizerKilledResponse = await _serverConnectionManager.GetResponseAsync<bool, KillAccountSynchronizerRequested>(new KillAccountSynchronizerRequested(Account.Id));
var folderStructures = (await _folderService.GetFolderStructureForAccountAsync(Account.Id, true)).Folders;
if (isSynchronizerKilledResponse.IsSuccess)
foreach (var folder in folderStructures)
{
await _accountService.DeleteAccountAsync(Account);
_dialogService.InfoBarMessage(Translator.Info_AccountDeletedTitle, string.Format(Translator.Info_AccountDeletedMessage, Account.Name), InfoBarMessageType.Success);
Messenger.Send(new BackBreadcrumNavigationRequested());
}
}
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
{
base.OnNavigatedTo(mode, parameters);
if (parameters is Guid accountId)
{
Account = await _accountService.GetAccountAsync(accountId);
IsFocusedInboxEnabled = Account.Preferences.IsFocusedInboxEnabled.GetValueOrDefault();
AreNotificationsEnabled = Account.Preferences.IsNotificationsEnabled;
IsSignatureEnabled = Account.Preferences.IsSignatureEnabled;
IsAppendMessageSettingVisible = Account.ProviderType == MailProviderType.IMAP4;
IsAppendMessageSettinEnabled = Account.Preferences.ShouldAppendMessagesToSentFolder;
IsTaskbarBadgeEnabled = Account.Preferences.IsTaskbarBadgeEnabled;
OnPropertyChanged(nameof(IsFocusedInboxSupportedForAccount));
var folderStructures = (await _folderService.GetFolderStructureForAccountAsync(Account.Id, true)).Folders;
foreach (var folder in folderStructures)
{
CurrentFolders.Add(folder);
}
}
}
protected override async void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
switch (e.PropertyName)
{
case nameof(IsFocusedInboxEnabled) when IsFocusedInboxSupportedForAccount:
Account.Preferences.IsFocusedInboxEnabled = IsFocusedInboxEnabled;
await _accountService.UpdateAccountAsync(Account);
break;
case nameof(AreNotificationsEnabled):
Account.Preferences.IsNotificationsEnabled = AreNotificationsEnabled;
await _accountService.UpdateAccountAsync(Account);
break;
case nameof(IsAppendMessageSettinEnabled):
Account.Preferences.ShouldAppendMessagesToSentFolder = IsAppendMessageSettinEnabled;
await _accountService.UpdateAccountAsync(Account);
break;
case nameof(IsSignatureEnabled):
Account.Preferences.IsSignatureEnabled = IsSignatureEnabled;
await _accountService.UpdateAccountAsync(Account);
break;
case nameof(IsTaskbarBadgeEnabled):
Account.Preferences.IsTaskbarBadgeEnabled = IsTaskbarBadgeEnabled;
await _accountService.UpdateAccountAsync(Account);
break;
CurrentFolders.Add(folder);
}
}
}
protected override async void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
switch (e.PropertyName)
{
case nameof(IsFocusedInboxEnabled) when IsFocusedInboxSupportedForAccount:
Account.Preferences.IsFocusedInboxEnabled = IsFocusedInboxEnabled;
await _accountService.UpdateAccountAsync(Account);
break;
case nameof(AreNotificationsEnabled):
Account.Preferences.IsNotificationsEnabled = AreNotificationsEnabled;
await _accountService.UpdateAccountAsync(Account);
break;
case nameof(IsAppendMessageSettinEnabled):
Account.Preferences.ShouldAppendMessagesToSentFolder = IsAppendMessageSettinEnabled;
await _accountService.UpdateAccountAsync(Account);
break;
case nameof(IsSignatureEnabled):
Account.Preferences.IsSignatureEnabled = IsSignatureEnabled;
await _accountService.UpdateAccountAsync(Account);
break;
case nameof(IsTaskbarBadgeEnabled):
Account.Preferences.IsTaskbarBadgeEnabled = IsTaskbarBadgeEnabled;
await _accountService.UpdateAccountAsync(Account);
break;
}
}
}

View File

@@ -5,7 +5,9 @@ using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.AppCenter.Crashes;
using Serilog;
using Wino.Core;
using Wino.Core.Domain;
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Entities.Shared;
@@ -22,368 +24,368 @@ using Wino.Messaging.Client.Navigation;
using Wino.Messaging.Server;
using Wino.Messaging.UI;
namespace Wino.Mail.ViewModels
namespace Wino.Mail.ViewModels;
public partial class AccountManagementViewModel : AccountManagementPageViewModelBase
{
public partial class AccountManagementViewModel : AccountManagementPageViewModelBase
private readonly ISpecialImapProviderConfigResolver _specialImapProviderConfigResolver;
private readonly IImapTestService _imapTestService;
public IMailDialogService MailDialogService { get; }
public AccountManagementViewModel(IMailDialogService dialogService,
IWinoServerConnectionManager winoServerConnectionManager,
INavigationService navigationService,
IAccountService accountService,
ISpecialImapProviderConfigResolver specialImapProviderConfigResolver,
IProviderService providerService,
IImapTestService imapTestService,
IStoreManagementService storeManagementService,
IAuthenticationProvider authenticationProvider,
IPreferencesService preferencesService) : base(dialogService, winoServerConnectionManager, navigationService, accountService, providerService, storeManagementService, authenticationProvider, preferencesService)
{
private readonly ISpecialImapProviderConfigResolver _specialImapProviderConfigResolver;
private readonly IImapTestService _imapTestService;
MailDialogService = dialogService;
_specialImapProviderConfigResolver = specialImapProviderConfigResolver;
_imapTestService = imapTestService;
}
public IMailDialogService MailDialogService { get; }
[RelayCommand]
private async Task CreateMergedAccountAsync()
{
var linkName = await DialogService.ShowTextInputDialogAsync(string.Empty, Translator.DialogMessage_CreateLinkedAccountTitle, Translator.DialogMessage_CreateLinkedAccountMessage, Translator.Buttons_Create);
public AccountManagementViewModel(IMailDialogService dialogService,
IWinoServerConnectionManager winoServerConnectionManager,
INavigationService navigationService,
IAccountService accountService,
ISpecialImapProviderConfigResolver specialImapProviderConfigResolver,
IProviderService providerService,
IImapTestService imapTestService,
IStoreManagementService storeManagementService,
IAuthenticationProvider authenticationProvider,
IPreferencesService preferencesService) : base(dialogService, winoServerConnectionManager, navigationService, accountService, providerService, storeManagementService, authenticationProvider, preferencesService)
if (string.IsNullOrEmpty(linkName)) return;
// Create arbitary empty merged inbox with an empty Guid and go to edit page.
var mergedInbox = new MergedInbox()
{
MailDialogService = dialogService;
_specialImapProviderConfigResolver = specialImapProviderConfigResolver;
_imapTestService = imapTestService;
Id = Guid.Empty,
Name = linkName
};
var mergedAccountProviderDetailViewModel = new MergedAccountProviderDetailViewModel(mergedInbox, new List<AccountProviderDetailViewModel>());
Messenger.Send(new BreadcrumbNavigationRequested(mergedAccountProviderDetailViewModel.MergedInbox.Name,
WinoPage.MergedAccountDetailsPage,
mergedAccountProviderDetailViewModel));
}
[RelayCommand]
private async Task AddNewAccountAsync()
{
if (IsAccountCreationBlocked)
{
var isPurchaseClicked = await DialogService.ShowConfirmationDialogAsync(Translator.DialogMessage_AccountLimitMessage, Translator.DialogMessage_AccountLimitTitle, Translator.Buttons_Purchase);
if (!isPurchaseClicked) return;
await PurchaseUnlimitedAccountAsync();
return;
}
[RelayCommand]
private async Task CreateMergedAccountAsync()
MailAccount createdAccount = null;
IAccountCreationDialog creationDialog = null;
try
{
var linkName = await DialogService.ShowTextInputDialogAsync(string.Empty, Translator.DialogMessage_CreateLinkedAccountTitle, Translator.DialogMessage_CreateLinkedAccountMessage, Translator.Buttons_Create);
var providers = ProviderService.GetAvailableProviders();
if (string.IsNullOrEmpty(linkName)) return;
// Select provider.
var accountCreationDialogResult = await MailDialogService.ShowAccountProviderSelectionDialogAsync(providers);
// Create arbitary empty merged inbox with an empty Guid and go to edit page.
var mergedInbox = new MergedInbox()
var accountCreationCancellationTokenSource = new CancellationTokenSource();
if (accountCreationDialogResult != null)
{
Id = Guid.Empty,
Name = linkName
};
creationDialog = MailDialogService.GetAccountCreationDialog(accountCreationDialogResult);
var mergedAccountProviderDetailViewModel = new MergedAccountProviderDetailViewModel(mergedInbox, new List<AccountProviderDetailViewModel>());
CustomServerInformation customServerInformation = null;
Messenger.Send(new BreadcrumbNavigationRequested(mergedAccountProviderDetailViewModel.MergedInbox.Name,
WinoPage.MergedAccountDetailsPage,
mergedAccountProviderDetailViewModel));
}
[RelayCommand]
private async Task AddNewAccountAsync()
{
if (IsAccountCreationBlocked)
{
var isPurchaseClicked = await DialogService.ShowConfirmationDialogAsync(Translator.DialogMessage_AccountLimitMessage, Translator.DialogMessage_AccountLimitTitle, Translator.Buttons_Purchase);
if (!isPurchaseClicked) return;
await PurchaseUnlimitedAccountAsync();
return;
}
MailAccount createdAccount = null;
IAccountCreationDialog creationDialog = null;
try
{
var providers = ProviderService.GetAvailableProviders();
// Select provider.
var accountCreationDialogResult = await MailDialogService.ShowAccountProviderSelectionDialogAsync(providers);
var accountCreationCancellationTokenSource = new CancellationTokenSource();
if (accountCreationDialogResult != null)
createdAccount = new MailAccount()
{
creationDialog = MailDialogService.GetAccountCreationDialog(accountCreationDialogResult);
ProviderType = accountCreationDialogResult.ProviderType,
Name = accountCreationDialogResult.AccountName,
SpecialImapProvider = accountCreationDialogResult.SpecialImapProviderDetails?.SpecialImapProvider ?? SpecialImapProvider.None,
Id = Guid.NewGuid()
};
CustomServerInformation customServerInformation = null;
await creationDialog.ShowDialogAsync(accountCreationCancellationTokenSource);
creationDialog.State = AccountCreationDialogState.SigningIn;
createdAccount = new MailAccount()
string tokenInformation = string.Empty;
// Custom server implementation requires more async waiting.
if (creationDialog is IImapAccountCreationDialog customServerDialog)
{
// Pass along the account properties and perform initial navigation on the imap frame.
customServerDialog.StartImapConnectionSetup(createdAccount);
customServerInformation = await customServerDialog.GetCustomServerInformationAsync()
?? throw new AccountSetupCanceledException();
// At this point connection is successful.
// Save the server setup information and later on we'll fetch folders.
customServerInformation.AccountId = createdAccount.Id;
createdAccount.Address = customServerInformation.Address;
createdAccount.ServerInformation = customServerInformation;
createdAccount.SenderName = customServerInformation.DisplayName;
}
else
{
// Hanle special imap providers like iCloud and Yahoo.
if (accountCreationDialogResult.SpecialImapProviderDetails != null)
{
ProviderType = accountCreationDialogResult.ProviderType,
Name = accountCreationDialogResult.AccountName,
SpecialImapProvider = accountCreationDialogResult.SpecialImapProviderDetails?.SpecialImapProvider ?? SpecialImapProvider.None,
Id = Guid.NewGuid()
};
await creationDialog.ShowDialogAsync(accountCreationCancellationTokenSource);
creationDialog.State = AccountCreationDialogState.SigningIn;
string tokenInformation = string.Empty;
// Custom server implementation requires more async waiting.
if (creationDialog is IImapAccountCreationDialog customServerDialog)
{
// Pass along the account properties and perform initial navigation on the imap frame.
customServerDialog.StartImapConnectionSetup(createdAccount);
customServerInformation = await customServerDialog.GetCustomServerInformationAsync()
?? throw new AccountSetupCanceledException();
// At this point connection is successful.
// Save the server setup information and later on we'll fetch folders.
// Special imap provider testing dialog. This is only available for iCloud and Yahoo.
customServerInformation = _specialImapProviderConfigResolver.GetServerInformation(createdAccount, accountCreationDialogResult);
customServerInformation.Id = Guid.NewGuid();
customServerInformation.AccountId = createdAccount.Id;
createdAccount.SenderName = accountCreationDialogResult.SpecialImapProviderDetails.SenderName;
createdAccount.Address = customServerInformation.Address;
createdAccount.ServerInformation = customServerInformation;
createdAccount.SenderName = customServerInformation.DisplayName;
await _imapTestService.TestImapConnectionAsync(customServerInformation, true);
}
else
{
// Hanle special imap providers like iCloud and Yahoo.
if (accountCreationDialogResult.SpecialImapProviderDetails != null)
{
// Special imap provider testing dialog. This is only available for iCloud and Yahoo.
customServerInformation = _specialImapProviderConfigResolver.GetServerInformation(createdAccount, accountCreationDialogResult);
customServerInformation.Id = Guid.NewGuid();
customServerInformation.AccountId = createdAccount.Id;
// OAuth authentication is handled here.
// Server authenticates, returns the token info here.
createdAccount.SenderName = accountCreationDialogResult.SpecialImapProviderDetails.SenderName;
createdAccount.Address = customServerInformation.Address;
var tokenInformationResponse = await WinoServerConnectionManager
.GetResponseAsync<TokenInformationEx, AuthorizationRequested>(new AuthorizationRequested(accountCreationDialogResult.ProviderType,
createdAccount,
createdAccount.ProviderType == MailProviderType.Gmail), accountCreationCancellationTokenSource.Token);
await _imapTestService.TestImapConnectionAsync(customServerInformation, true);
}
else
{
// OAuth authentication is handled here.
// Server authenticates, returns the token info here.
if (creationDialog.State == AccountCreationDialogState.Canceled)
throw new AccountSetupCanceledException();
var tokenInformationResponse = await WinoServerConnectionManager
.GetResponseAsync<TokenInformationEx, AuthorizationRequested>(new AuthorizationRequested(accountCreationDialogResult.ProviderType,
createdAccount,
createdAccount.ProviderType == MailProviderType.Gmail), accountCreationCancellationTokenSource.Token);
if (!tokenInformationResponse.IsSuccess)
throw new Exception(tokenInformationResponse.Message);
if (creationDialog.State == AccountCreationDialogState.Canceled)
throw new AccountSetupCanceledException();
createdAccount.Address = tokenInformationResponse.Data.AccountAddress;
if (!tokenInformationResponse.IsSuccess)
throw new Exception(tokenInformationResponse.Message);
createdAccount.Address = tokenInformationResponse.Data.AccountAddress;
tokenInformationResponse.ThrowIfFailed();
}
tokenInformationResponse.ThrowIfFailed();
}
}
// Address is still doesn't have a value for API synchronizers.
// It'll be synchronized with profile information.
// Address is still doesn't have a value for API synchronizers.
// It'll be synchronized with profile information.
await AccountService.CreateAccountAsync(createdAccount, customServerInformation);
await AccountService.CreateAccountAsync(createdAccount, customServerInformation);
// Local account has been created.
// Local account has been created.
// Sync profile information if supported.
if (createdAccount.IsProfileInfoSyncSupported)
{
// Start profile information synchronization.
// It's only available for Outlook and Gmail synchronizers.
// Sync profile information if supported.
if (createdAccount.IsProfileInfoSyncSupported)
{
// Start profile information synchronization.
// It's only available for Outlook and Gmail synchronizers.
var profileSyncOptions = new MailSynchronizationOptions()
{
AccountId = createdAccount.Id,
Type = MailSynchronizationType.UpdateProfile
};
var profileSynchronizationResponse = await WinoServerConnectionManager.GetResponseAsync<MailSynchronizationResult, NewMailSynchronizationRequested>(new NewMailSynchronizationRequested(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;
if (!string.IsNullOrEmpty(profileSynchronizationResult.ProfileInformation.AccountAddress))
{
createdAccount.Address = profileSynchronizationResult.ProfileInformation.AccountAddress;
}
await AccountService.UpdateProfileInformationAsync(createdAccount.Id, profileSynchronizationResult.ProfileInformation);
}
if (creationDialog is IImapAccountCreationDialog customServerAccountCreationDialog)
customServerAccountCreationDialog.ShowPreparingFolders();
else
creationDialog.State = AccountCreationDialogState.PreparingFolders;
// Start synchronizing folders.
var folderSyncOptions = new MailSynchronizationOptions()
var profileSyncOptions = new MailSynchronizationOptions()
{
AccountId = createdAccount.Id,
Type = MailSynchronizationType.FoldersOnly
Type = MailSynchronizationType.UpdateProfile
};
var folderSynchronizationResponse = await WinoServerConnectionManager.GetResponseAsync<MailSynchronizationResult, NewMailSynchronizationRequested>(new NewMailSynchronizationRequested(folderSyncOptions, SynchronizationSource.Client));
var profileSynchronizationResponse = await WinoServerConnectionManager.GetResponseAsync<MailSynchronizationResult, NewMailSynchronizationRequested>(new NewMailSynchronizationRequested(profileSyncOptions, SynchronizationSource.Client));
var folderSynchronizationResult = folderSynchronizationResponse.Data;
var profileSynchronizationResult = profileSynchronizationResponse.Data;
if (folderSynchronizationResult == null || folderSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
throw new Exception(Translator.Exception_FailedToSynchronizeFolders);
if (profileSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
throw new Exception(Translator.Exception_FailedToSynchronizeProfileInformation);
// Sync aliases if supported.
if (createdAccount.IsAliasSyncSupported)
createdAccount.SenderName = profileSynchronizationResult.ProfileInformation.SenderName;
createdAccount.Base64ProfilePictureData = profileSynchronizationResult.ProfileInformation.Base64ProfilePictureData;
if (!string.IsNullOrEmpty(profileSynchronizationResult.ProfileInformation.AccountAddress))
{
// Try to synchronize aliases for the account.
var aliasSyncOptions = new MailSynchronizationOptions()
{
AccountId = createdAccount.Id,
Type = MailSynchronizationType.Alias
};
var aliasSyncResponse = await WinoServerConnectionManager.GetResponseAsync<MailSynchronizationResult, NewMailSynchronizationRequested>(new NewMailSynchronizationRequested(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);
createdAccount.Address = profileSynchronizationResult.ProfileInformation.AccountAddress;
}
// Send changes to listeners.
ReportUIChange(new AccountCreatedMessage(createdAccount));
// Notify success.
DialogService.InfoBarMessage(Translator.Info_AccountCreatedTitle, string.Format(Translator.Info_AccountCreatedMessage, createdAccount.Address), InfoBarMessageType.Success);
await AccountService.UpdateProfileInformationAsync(createdAccount.Id, profileSynchronizationResult.ProfileInformation);
}
}
catch (AccountSetupCanceledException)
{
// Ignore
}
catch (Exception ex) when (ex.Message.Contains(nameof(AccountSetupCanceledException)))
{
// Ignore
}
catch (ImapClientPoolException clientPoolException)
{
DialogService.InfoBarMessage(Translator.Info_AccountCreationFailedTitle, clientPoolException.InnerException.Message, InfoBarMessageType.Error);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to create account.");
DialogService.InfoBarMessage(Translator.Info_AccountCreationFailedTitle, ex.Message, InfoBarMessageType.Error);
if (creationDialog is IImapAccountCreationDialog customServerAccountCreationDialog)
customServerAccountCreationDialog.ShowPreparingFolders();
else
creationDialog.State = AccountCreationDialogState.PreparingFolders;
// Delete account in case of failure.
if (createdAccount != null)
// Start synchronizing folders.
var folderSyncOptions = new MailSynchronizationOptions()
{
await AccountService.DeleteAccountAsync(createdAccount);
}
}
finally
{
creationDialog?.Complete(false);
}
}
AccountId = createdAccount.Id,
Type = MailSynchronizationType.FoldersOnly
};
[RelayCommand]
private void EditMergedAccounts(MergedAccountProviderDetailViewModel mergedAccountProviderDetailViewModel)
{
Messenger.Send(new BreadcrumbNavigationRequested(mergedAccountProviderDetailViewModel.MergedInbox.Name,
WinoPage.MergedAccountDetailsPage,
mergedAccountProviderDetailViewModel));
}
var folderSynchronizationResponse = await WinoServerConnectionManager.GetResponseAsync<MailSynchronizationResult, NewMailSynchronizationRequested>(new NewMailSynchronizationRequested(folderSyncOptions, SynchronizationSource.Client));
[RelayCommand(CanExecute = nameof(CanReorderAccounts))]
private Task ReorderAccountsAsync() => MailDialogService.ShowAccountReorderDialogAsync(availableAccounts: Accounts);
var folderSynchronizationResult = folderSynchronizationResponse.Data;
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
{
base.OnNavigatedFrom(mode, parameters);
if (folderSynchronizationResult == null || folderSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
throw new Exception(Translator.Exception_FailedToSynchronizeFolders);
Accounts.CollectionChanged -= AccountCollectionChanged;
PropertyChanged -= PagePropertyChanged;
}
private void AccountCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(nameof(HasAccountsDefined));
OnPropertyChanged(nameof(UsedAccountsString));
OnPropertyChanged(nameof(IsAccountCreationAlmostOnLimit));
ReorderAccountsCommand.NotifyCanExecuteChanged();
}
private void PagePropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(StartupAccount) && StartupAccount != null)
{
PreferencesService.StartupEntityId = StartupAccount.StartupEntityId;
}
}
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
{
base.OnNavigatedTo(mode, parameters);
Accounts.CollectionChanged -= AccountCollectionChanged;
Accounts.CollectionChanged += AccountCollectionChanged;
await InitializeAccountsAsync();
PropertyChanged -= PagePropertyChanged;
PropertyChanged += PagePropertyChanged;
}
public override async Task InitializeAccountsAsync()
{
StartupAccount = null;
Accounts.Clear();
var accounts = await AccountService.GetAccountsAsync().ConfigureAwait(false);
// Group accounts and display merged ones at the top.
var groupedAccounts = accounts.GroupBy(a => a.MergedInboxId);
await ExecuteUIThread(() =>
{
foreach (var accountGroup in groupedAccounts)
// Sync aliases if supported.
if (createdAccount.IsAliasSyncSupported)
{
var mergedInboxId = accountGroup.Key;
// Try to synchronize aliases for the account.
if (mergedInboxId == null)
var aliasSyncOptions = new MailSynchronizationOptions()
{
foreach (var account in accountGroup)
{
var accountDetails = GetAccountProviderDetails(account);
AccountId = createdAccount.Id,
Type = MailSynchronizationType.Alias
};
Accounts.Add(accountDetails);
}
}
else
{
var mergedInbox = accountGroup.First(a => a.MergedInboxId == mergedInboxId).MergedInbox;
var aliasSyncResponse = await WinoServerConnectionManager.GetResponseAsync<MailSynchronizationResult, NewMailSynchronizationRequested>(new NewMailSynchronizationRequested(aliasSyncOptions, SynchronizationSource.Client));
var aliasSynchronizationResult = folderSynchronizationResponse.Data;
var holdingAccountProviderDetails = accountGroup.Select(a => GetAccountProviderDetails(a)).ToList();
var mergedAccountViewModel = new MergedAccountProviderDetailViewModel(mergedInbox, holdingAccountProviderDetails);
Accounts.Add(mergedAccountViewModel);
}
if (aliasSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
throw new Exception(Translator.Exception_FailedToSynchronizeAliases);
}
// Handle startup entity.
if (PreferencesService.StartupEntityId != null)
else
{
StartupAccount = Accounts.FirstOrDefault(a => a.StartupEntityId == PreferencesService.StartupEntityId);
// 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);
}
});
// Send changes to listeners.
ReportUIChange(new AccountCreatedMessage(createdAccount));
await ManageStorePurchasesAsync().ConfigureAwait(false);
// Notify success.
DialogService.InfoBarMessage(Translator.Info_AccountCreatedTitle, string.Format(Translator.Info_AccountCreatedMessage, createdAccount.Address), InfoBarMessageType.Success);
}
}
catch (AccountSetupCanceledException)
{
// Ignore
}
catch (Exception ex) when (ex.Message.Contains(nameof(AccountSetupCanceledException)))
{
// Ignore
}
catch (ImapClientPoolException clientPoolException)
{
DialogService.InfoBarMessage(Translator.Info_AccountCreationFailedTitle, clientPoolException.InnerException.Message, InfoBarMessageType.Error);
}
catch (Exception ex)
{
Log.Error(ex, WinoErrors.AccountCreation);
Crashes.TrackError(ex);
DialogService.InfoBarMessage(Translator.Info_AccountCreationFailedTitle, ex.Message, InfoBarMessageType.Error);
// Delete account in case of failure.
if (createdAccount != null)
{
await AccountService.DeleteAccountAsync(createdAccount);
}
}
finally
{
creationDialog?.Complete(false);
}
}
[RelayCommand]
private void EditMergedAccounts(MergedAccountProviderDetailViewModel mergedAccountProviderDetailViewModel)
{
Messenger.Send(new BreadcrumbNavigationRequested(mergedAccountProviderDetailViewModel.MergedInbox.Name,
WinoPage.MergedAccountDetailsPage,
mergedAccountProviderDetailViewModel));
}
[RelayCommand(CanExecute = nameof(CanReorderAccounts))]
private Task ReorderAccountsAsync() => MailDialogService.ShowAccountReorderDialogAsync(availableAccounts: Accounts);
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
{
base.OnNavigatedFrom(mode, parameters);
Accounts.CollectionChanged -= AccountCollectionChanged;
PropertyChanged -= PagePropertyChanged;
}
private void AccountCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(nameof(HasAccountsDefined));
OnPropertyChanged(nameof(UsedAccountsString));
OnPropertyChanged(nameof(IsAccountCreationAlmostOnLimit));
ReorderAccountsCommand.NotifyCanExecuteChanged();
}
private void PagePropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(StartupAccount) && StartupAccount != null)
{
PreferencesService.StartupEntityId = StartupAccount.StartupEntityId;
}
}
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
{
base.OnNavigatedTo(mode, parameters);
Accounts.CollectionChanged -= AccountCollectionChanged;
Accounts.CollectionChanged += AccountCollectionChanged;
await InitializeAccountsAsync();
PropertyChanged -= PagePropertyChanged;
PropertyChanged += PagePropertyChanged;
}
public override async Task InitializeAccountsAsync()
{
StartupAccount = null;
Accounts.Clear();
var accounts = await AccountService.GetAccountsAsync().ConfigureAwait(false);
// Group accounts and display merged ones at the top.
var groupedAccounts = accounts.GroupBy(a => a.MergedInboxId);
await ExecuteUIThread(() =>
{
foreach (var accountGroup in groupedAccounts)
{
var mergedInboxId = accountGroup.Key;
if (mergedInboxId == null)
{
foreach (var account in accountGroup)
{
var accountDetails = GetAccountProviderDetails(account);
Accounts.Add(accountDetails);
}
}
else
{
var mergedInbox = accountGroup.First(a => a.MergedInboxId == mergedInboxId).MergedInbox;
var holdingAccountProviderDetails = accountGroup.Select(a => GetAccountProviderDetails(a)).ToList();
var mergedAccountViewModel = new MergedAccountProviderDetailViewModel(mergedInbox, holdingAccountProviderDetails);
Accounts.Add(mergedAccountViewModel);
}
}
// Handle startup entity.
if (PreferencesService.StartupEntityId != null)
{
StartupAccount = Accounts.FirstOrDefault(a => a.StartupEntityId == PreferencesService.StartupEntityId);
}
});
await ManageStorePurchasesAsync().ConfigureAwait(false);
}
}

View File

@@ -14,142 +14,141 @@ using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Messaging.Server;
namespace Wino.Mail.ViewModels
namespace Wino.Mail.ViewModels;
public partial class AliasManagementPageViewModel : MailBaseViewModel
{
public partial class AliasManagementPageViewModel : MailBaseViewModel
private readonly IMailDialogService _dialogService;
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(IMailDialogService dialogService,
IAccountService accountService,
IWinoServerConnectionManager winoServerConnectionManager)
{
private readonly IMailDialogService _dialogService;
private readonly IAccountService _accountService;
private readonly IWinoServerConnectionManager _winoServerConnectionManager;
_dialogService = dialogService;
_accountService = accountService;
_winoServerConnectionManager = winoServerConnectionManager;
}
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(CanSynchronizeAliases))]
private MailAccount account;
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
{
base.OnNavigatedTo(mode, parameters);
[ObservableProperty]
private List<MailAccountAlias> accountAliases = [];
if (parameters is Guid accountId)
Account = await _accountService.GetAccountAsync(accountId);
public bool CanSynchronizeAliases => Account?.IsAliasSyncSupported ?? false;
if (Account == null) return;
public AliasManagementPageViewModel(IMailDialogService dialogService,
IAccountService accountService,
IWinoServerConnectionManager winoServerConnectionManager)
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 =>
{
_dialogService = dialogService;
_accountService = accountService;
_winoServerConnectionManager = winoServerConnectionManager;
}
a.IsPrimary = a == alias;
});
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
await _accountService.UpdateAccountAliasesAsync(Account.Id, AccountAliases);
await LoadAliasesAsync();
}
[RelayCommand]
private async Task SyncAliasesAsync()
{
if (!CanSynchronizeAliases) return;
var aliasSyncOptions = new MailSynchronizationOptions()
{
base.OnNavigatedTo(mode, parameters);
AccountId = Account.Id,
Type = MailSynchronizationType.Alias
};
if (parameters is Guid accountId)
Account = await _accountService.GetAccountAsync(accountId);
if (Account == null) return;
var aliasSyncResponse = await _winoServerConnectionManager.GetResponseAsync<MailSynchronizationResult, NewMailSynchronizationRequested>(new NewMailSynchronizationRequested(aliasSyncOptions, SynchronizationSource.Client));
if (aliasSyncResponse.IsSuccess)
await LoadAliasesAsync();
}
else
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error, aliasSyncResponse.Message, InfoBarMessageType.Error);
}
private async Task 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))
{
AccountAliases = await _accountService.GetAccountAliasesAsync(Account.Id);
await _dialogService.ShowMessageAsync(Translator.DialogMessage_AliasExistsTitle,
Translator.DialogMessage_AliasExistsMessage,
WinoCustomMessageDialogIcon.Warning);
return;
}
[RelayCommand]
private async Task SetAliasPrimaryAsync(MailAccountAlias alias)
// Validate all addresses.
if (!EmailValidator.Validate(newAlias.AliasAddress) || (!string.IsNullOrEmpty(newAlias.ReplyToAddress) && !EmailValidator.Validate(newAlias.ReplyToAddress)))
{
if (alias.IsPrimary) return;
AccountAliases.ForEach(a =>
{
a.IsPrimary = a == alias;
});
await _accountService.UpdateAccountAliasesAsync(Account.Id, AccountAliases);
await LoadAliasesAsync();
await _dialogService.ShowMessageAsync(Translator.DialogMessage_InvalidAliasMessage,
Translator.DialogMessage_InvalidAliasTitle,
WinoCustomMessageDialogIcon.Warning);
return;
}
[RelayCommand]
private async Task SyncAliasesAsync()
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)
{
if (!CanSynchronizeAliases) return;
var aliasSyncOptions = new MailSynchronizationOptions()
{
AccountId = Account.Id,
Type = MailSynchronizationType.Alias
};
var aliasSyncResponse = await _winoServerConnectionManager.GetResponseAsync<MailSynchronizationResult, NewMailSynchronizationRequested>(new NewMailSynchronizationRequested(aliasSyncOptions, SynchronizationSource.Client));
if (aliasSyncResponse.IsSuccess)
await LoadAliasesAsync();
else
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error, aliasSyncResponse.Message, InfoBarMessageType.Error);
await _dialogService.ShowMessageAsync(Translator.Info_CantDeletePrimaryAliasMessage,
Translator.GeneralTitle_Warning,
WinoCustomMessageDialogIcon.Warning);
return;
}
[RelayCommand]
private async Task AddNewAliasAsync()
// Root aliases can't be deleted.
if (alias.IsRootAlias)
{
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,
WinoCustomMessageDialogIcon.Warning);
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,
WinoCustomMessageDialogIcon.Warning);
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();
await _dialogService.ShowMessageAsync(Translator.DialogMessage_CantDeleteRootAliasTitle,
Translator.DialogMessage_CantDeleteRootAliasMessage,
WinoCustomMessageDialogIcon.Warning);
return;
}
[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,
WinoCustomMessageDialogIcon.Warning);
return;
}
// Root aliases can't be deleted.
if (alias.IsRootAlias)
{
await _dialogService.ShowMessageAsync(Translator.DialogMessage_CantDeleteRootAliasTitle,
Translator.DialogMessage_CantDeleteRootAliasMessage,
WinoCustomMessageDialogIcon.Warning);
return;
}
await _accountService.DeleteAccountAliasAsync(alias.Id);
await LoadAliasesAsync();
}
await _accountService.DeleteAccountAliasAsync(alias.Id);
await LoadAliasesAsync();
}
}

View File

@@ -9,134 +9,133 @@ using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Navigation;
using Wino.Messaging.Server;
namespace Wino.Mail.ViewModels
namespace Wino.Mail.ViewModels;
public partial class AppPreferencesPageViewModel : MailBaseViewModel
{
public partial class AppPreferencesPageViewModel : MailBaseViewModel
public IPreferencesService PreferencesService { get; }
[ObservableProperty]
private List<string> _appTerminationBehavior;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsStartupBehaviorDisabled))]
[NotifyPropertyChangedFor(nameof(IsStartupBehaviorEnabled))]
private StartupBehaviorResult startupBehaviorResult;
public bool IsStartupBehaviorDisabled => !IsStartupBehaviorEnabled;
public bool IsStartupBehaviorEnabled => StartupBehaviorResult == StartupBehaviorResult.Enabled;
private string _selectedAppTerminationBehavior;
public string SelectedAppTerminationBehavior
{
public IPreferencesService PreferencesService { get; }
[ObservableProperty]
private List<string> _appTerminationBehavior;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsStartupBehaviorDisabled))]
[NotifyPropertyChangedFor(nameof(IsStartupBehaviorEnabled))]
private StartupBehaviorResult startupBehaviorResult;
public bool IsStartupBehaviorDisabled => !IsStartupBehaviorEnabled;
public bool IsStartupBehaviorEnabled => StartupBehaviorResult == StartupBehaviorResult.Enabled;
private string _selectedAppTerminationBehavior;
public string SelectedAppTerminationBehavior
get => _selectedAppTerminationBehavior;
set
{
get => _selectedAppTerminationBehavior;
set
{
SetProperty(ref _selectedAppTerminationBehavior, value);
SetProperty(ref _selectedAppTerminationBehavior, value);
PreferencesService.ServerTerminationBehavior = (ServerBackgroundMode)AppTerminationBehavior.IndexOf(value);
}
}
private readonly IMailDialogService _dialogService;
private readonly IWinoServerConnectionManager _winoServerConnectionManager;
private readonly IStartupBehaviorService _startupBehaviorService;
public AppPreferencesPageViewModel(IMailDialogService dialogService,
IPreferencesService preferencesService,
IWinoServerConnectionManager winoServerConnectionManager,
IStartupBehaviorService startupBehaviorService)
{
_dialogService = dialogService;
PreferencesService = preferencesService;
_winoServerConnectionManager = winoServerConnectionManager;
_startupBehaviorService = startupBehaviorService;
// Load the app termination behavior options
_appTerminationBehavior =
[
Translator.SettingsAppPreferences_ServerBackgroundingMode_MinimizeTray_Title, // "Minimize to tray"
Translator.SettingsAppPreferences_ServerBackgroundingMode_Invisible_Title, // "Invisible"
Translator.SettingsAppPreferences_ServerBackgroundingMode_Terminate_Title // "Terminate"
];
SelectedAppTerminationBehavior = _appTerminationBehavior[(int)PreferencesService.ServerTerminationBehavior];
}
[RelayCommand]
private async Task ToggleStartupBehaviorAsync()
{
if (IsStartupBehaviorEnabled)
{
await DisableStartupAsync();
}
else
{
await EnableStartupAsync();
}
OnPropertyChanged(nameof(IsStartupBehaviorEnabled));
}
private async Task EnableStartupAsync()
{
StartupBehaviorResult = await _startupBehaviorService.ToggleStartupBehavior(true);
NotifyCurrentStartupState();
}
private async Task DisableStartupAsync()
{
StartupBehaviorResult = await _startupBehaviorService.ToggleStartupBehavior(false);
NotifyCurrentStartupState();
}
private void NotifyCurrentStartupState()
{
if (StartupBehaviorResult == StartupBehaviorResult.Enabled)
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Info, Translator.SettingsAppPreferences_StartupBehavior_Enabled, InfoBarMessageType.Success);
}
else if (StartupBehaviorResult == StartupBehaviorResult.Disabled)
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Info, Translator.SettingsAppPreferences_StartupBehavior_Disabled, InfoBarMessageType.Warning);
}
else if (StartupBehaviorResult == StartupBehaviorResult.DisabledByPolicy)
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Info, Translator.SettingsAppPreferences_StartupBehavior_DisabledByPolicy, InfoBarMessageType.Warning);
}
else if (StartupBehaviorResult == StartupBehaviorResult.DisabledByUser)
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Info, Translator.SettingsAppPreferences_StartupBehavior_DisabledByUser, InfoBarMessageType.Warning);
}
else
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error, Translator.SettingsAppPreferences_StartupBehavior_FatalError, InfoBarMessageType.Error);
}
}
protected override async void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.PropertyName == nameof(SelectedAppTerminationBehavior))
{
var terminationModeChangedResult = await _winoServerConnectionManager.GetResponseAsync<bool, ServerTerminationModeChanged>(new ServerTerminationModeChanged(PreferencesService.ServerTerminationBehavior));
if (!terminationModeChangedResult.IsSuccess)
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error, terminationModeChangedResult.Message, InfoBarMessageType.Error);
}
}
}
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
{
base.OnNavigatedTo(mode, parameters);
StartupBehaviorResult = await _startupBehaviorService.GetCurrentStartupBehaviorAsync();
PreferencesService.ServerTerminationBehavior = (ServerBackgroundMode)AppTerminationBehavior.IndexOf(value);
}
}
private readonly IMailDialogService _dialogService;
private readonly IWinoServerConnectionManager _winoServerConnectionManager;
private readonly IStartupBehaviorService _startupBehaviorService;
public AppPreferencesPageViewModel(IMailDialogService dialogService,
IPreferencesService preferencesService,
IWinoServerConnectionManager winoServerConnectionManager,
IStartupBehaviorService startupBehaviorService)
{
_dialogService = dialogService;
PreferencesService = preferencesService;
_winoServerConnectionManager = winoServerConnectionManager;
_startupBehaviorService = startupBehaviorService;
// Load the app termination behavior options
_appTerminationBehavior =
[
Translator.SettingsAppPreferences_ServerBackgroundingMode_MinimizeTray_Title, // "Minimize to tray"
Translator.SettingsAppPreferences_ServerBackgroundingMode_Invisible_Title, // "Invisible"
Translator.SettingsAppPreferences_ServerBackgroundingMode_Terminate_Title // "Terminate"
];
SelectedAppTerminationBehavior = _appTerminationBehavior[(int)PreferencesService.ServerTerminationBehavior];
}
[RelayCommand]
private async Task ToggleStartupBehaviorAsync()
{
if (IsStartupBehaviorEnabled)
{
await DisableStartupAsync();
}
else
{
await EnableStartupAsync();
}
OnPropertyChanged(nameof(IsStartupBehaviorEnabled));
}
private async Task EnableStartupAsync()
{
StartupBehaviorResult = await _startupBehaviorService.ToggleStartupBehavior(true);
NotifyCurrentStartupState();
}
private async Task DisableStartupAsync()
{
StartupBehaviorResult = await _startupBehaviorService.ToggleStartupBehavior(false);
NotifyCurrentStartupState();
}
private void NotifyCurrentStartupState()
{
if (StartupBehaviorResult == StartupBehaviorResult.Enabled)
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Info, Translator.SettingsAppPreferences_StartupBehavior_Enabled, InfoBarMessageType.Success);
}
else if (StartupBehaviorResult == StartupBehaviorResult.Disabled)
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Info, Translator.SettingsAppPreferences_StartupBehavior_Disabled, InfoBarMessageType.Warning);
}
else if (StartupBehaviorResult == StartupBehaviorResult.DisabledByPolicy)
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Info, Translator.SettingsAppPreferences_StartupBehavior_DisabledByPolicy, InfoBarMessageType.Warning);
}
else if (StartupBehaviorResult == StartupBehaviorResult.DisabledByUser)
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Info, Translator.SettingsAppPreferences_StartupBehavior_DisabledByUser, InfoBarMessageType.Warning);
}
else
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error, Translator.SettingsAppPreferences_StartupBehavior_FatalError, InfoBarMessageType.Error);
}
}
protected override async void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.PropertyName == nameof(SelectedAppTerminationBehavior))
{
var terminationModeChangedResult = await _winoServerConnectionManager.GetResponseAsync<bool, ServerTerminationModeChanged>(new ServerTerminationModeChanged(PreferencesService.ServerTerminationBehavior));
if (!terminationModeChangedResult.IsSuccess)
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error, terminationModeChangedResult.Message, InfoBarMessageType.Error);
}
}
}
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
{
base.OnNavigatedTo(mode, parameters);
StartupBehaviorResult = await _startupBehaviorService.GetCurrentStartupBehaviorAsync();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -10,506 +10,505 @@ using Wino.Core.Domain.Models.Comparers;
using Wino.Core.Domain.Models.MailItem;
using Wino.Mail.ViewModels.Data;
namespace Wino.Mail.ViewModels.Collections
namespace Wino.Mail.ViewModels.Collections;
public class WinoMailCollection
{
public class WinoMailCollection
// We cache each mail copy id for faster access on updates.
// If the item provider here for update or removal doesn't exist here
// we can ignore the operation.
public HashSet<Guid> MailCopyIdHashSet = [];
public event EventHandler<IMailItem> MailItemRemoved;
private ListItemComparer listComparer = new ListItemComparer();
private readonly ObservableGroupedCollection<object, IMailItem> _mailItemSource = new ObservableGroupedCollection<object, IMailItem>();
public ReadOnlyObservableGroupedCollection<object, IMailItem> MailItems { get; }
/// <summary>
/// Property that defines how the item sorting should be done in the collection.
/// </summary>
public SortingOptionType SortingType { get; set; }
/// <summary>
/// Threading strategy that will help thread items according to the account type.
/// </summary>
public IThreadingStrategyProvider ThreadingStrategyProvider { get; set; }
/// <summary>
/// Automatically deletes single mail items after the delete operation or thread->single transition.
/// This is useful when reply draft is discarded in the thread. Only enabled for Draft folder for now.
/// </summary>
public bool PruneSingleNonDraftItems { get; set; }
public int Count => _mailItemSource.Count;
public IDispatcher CoreDispatcher { get; set; }
public WinoMailCollection()
{
// We cache each mail copy id for faster access on updates.
// If the item provider here for update or removal doesn't exist here
// we can ignore the operation.
MailItems = new ReadOnlyObservableGroupedCollection<object, IMailItem>(_mailItemSource);
}
public HashSet<Guid> MailCopyIdHashSet = [];
public void Clear() => _mailItemSource.Clear();
public event EventHandler<IMailItem> MailItemRemoved;
private object GetGroupingKey(IMailItem mailItem)
{
if (SortingType == SortingOptionType.ReceiveDate)
return mailItem.CreationDate.ToLocalTime().Date;
else
return mailItem.FromName;
}
private ListItemComparer listComparer = new ListItemComparer();
private readonly ObservableGroupedCollection<object, IMailItem> _mailItemSource = new ObservableGroupedCollection<object, IMailItem>();
public ReadOnlyObservableGroupedCollection<object, IMailItem> MailItems { get; }
/// <summary>
/// Property that defines how the item sorting should be done in the collection.
/// </summary>
public SortingOptionType SortingType { get; set; }
/// <summary>
/// Threading strategy that will help thread items according to the account type.
/// </summary>
public IThreadingStrategyProvider ThreadingStrategyProvider { get; set; }
/// <summary>
/// Automatically deletes single mail items after the delete operation or thread->single transition.
/// This is useful when reply draft is discarded in the thread. Only enabled for Draft folder for now.
/// </summary>
public bool PruneSingleNonDraftItems { get; set; }
public int Count => _mailItemSource.Count;
public IDispatcher CoreDispatcher { get; set; }
public WinoMailCollection()
private void UpdateUniqueIdHashes(IMailHashContainer itemContainer, bool isAdd)
{
foreach (var item in itemContainer.GetContainingIds())
{
MailItems = new ReadOnlyObservableGroupedCollection<object, IMailItem>(_mailItemSource);
}
public void Clear() => _mailItemSource.Clear();
private object GetGroupingKey(IMailItem mailItem)
{
if (SortingType == SortingOptionType.ReceiveDate)
return mailItem.CreationDate.ToLocalTime().Date;
else
return mailItem.FromName;
}
private void UpdateUniqueIdHashes(IMailHashContainer itemContainer, bool isAdd)
{
foreach (var item in itemContainer.GetContainingIds())
if (isAdd)
{
if (isAdd)
{
MailCopyIdHashSet.Add(item);
}
else
{
MailCopyIdHashSet.Remove(item);
}
}
}
private void InsertItemInternal(object groupKey, IMailItem mailItem)
{
UpdateUniqueIdHashes(mailItem, true);
if (mailItem is MailCopy mailCopy)
{
_mailItemSource.InsertItem(groupKey, listComparer, new MailItemViewModel(mailCopy), listComparer.GetItemComparer());
}
else if (mailItem is ThreadMailItem threadMailItem)
{
_mailItemSource.InsertItem(groupKey, listComparer, new ThreadMailItemViewModel(threadMailItem), listComparer.GetItemComparer());
MailCopyIdHashSet.Add(item);
}
else
{
_mailItemSource.InsertItem(groupKey, listComparer, mailItem, listComparer.GetItemComparer());
MailCopyIdHashSet.Remove(item);
}
}
}
private void RemoveItemInternal(ObservableGroup<object, IMailItem> group, IMailItem mailItem)
private void InsertItemInternal(object groupKey, IMailItem mailItem)
{
UpdateUniqueIdHashes(mailItem, true);
if (mailItem is MailCopy mailCopy)
{
UpdateUniqueIdHashes(mailItem, false);
MailItemRemoved?.Invoke(this, mailItem);
group.Remove(mailItem);
if (group.Count == 0)
{
_mailItemSource.RemoveGroup(group.Key);
}
_mailItemSource.InsertItem(groupKey, listComparer, new MailItemViewModel(mailCopy), listComparer.GetItemComparer());
}
public async Task AddAsync(MailCopy addedItem)
else if (mailItem is ThreadMailItem threadMailItem)
{
// Check all items for whether this item should be threaded with them.
bool shouldExit = false;
var groupCount = _mailItemSource.Count;
var addedAccountProviderType = addedItem.AssignedAccount.ProviderType;
var threadingStrategy = ThreadingStrategyProvider?.GetStrategy(addedAccountProviderType);
for (int i = 0; i < groupCount; i++)
{
if (shouldExit) break;
var group = _mailItemSource[i];
for (int k = 0; k < group.Count; k++)
{
var item = group[k];
if (threadingStrategy?.ShouldThreadWithItem(addedItem, item) ?? false)
{
shouldExit = true;
if (item is ThreadMailItemViewModel threadMailItemViewModel)
{
// Item belongs to existing thread.
/* Add original item to the thread.
* If new group key is not the same as existing thread:
* -> Remove the whole thread from list
* -> Add the thread to the list again for sorting.
* Update thread properties.
*/
var existingGroupKey = GetGroupingKey(threadMailItemViewModel);
await ExecuteUIThread(() => { threadMailItemViewModel.AddMailItemViewModel(addedItem); });
var newGroupKey = GetGroupingKey(threadMailItemViewModel);
if (!existingGroupKey.Equals(newGroupKey))
{
var mailThreadItems = threadMailItemViewModel.GetThreadMailItem();
await ExecuteUIThread(() =>
{
// Group must be changed for this thread.
// Remove the thread first.
RemoveItemInternal(group, threadMailItemViewModel);
// Insert new view model because the previous one might've been deleted with the group.
InsertItemInternal(newGroupKey, new ThreadMailItemViewModel(mailThreadItems));
});
}
else
{
await ExecuteUIThread(() => { threadMailItemViewModel.NotifyPropertyChanges(); });
}
UpdateUniqueIdHashes(addedItem, true);
break;
}
else
{
// Item belongs to a single mail item that is not threaded yet.
// Same item might've been tried to added as well.
// In that case we must just update the item but not thread it.
/* Remove target item.
* Create a new thread with both items.
* Add new thread to the list.
*/
if (item.Id == addedItem.Id)
{
// Item is already added to the list.
// We need to update the copy it holds.
if (item is MailItemViewModel itemViewModel)
{
await ExecuteUIThread(() => { itemViewModel.MailCopy = addedItem; });
UpdateUniqueIdHashes(itemViewModel, false);
UpdateUniqueIdHashes(addedItem, true);
}
}
else
{
// Single item that must be threaded together with added item.
var threadMailItem = new ThreadMailItem();
await ExecuteUIThread(() =>
{
threadMailItem.AddThreadItem(item);
threadMailItem.AddThreadItem(addedItem);
if (threadMailItem.ThreadItems.Count == 1) return;
var newGroupKey = GetGroupingKey(threadMailItem);
RemoveItemInternal(group, item);
InsertItemInternal(newGroupKey, threadMailItem);
});
}
break;
}
}
else
{
// Update properties.
if (item.Id == addedItem.Id && item is MailItemViewModel itemViewModel)
{
UpdateUniqueIdHashes(itemViewModel, false);
UpdateUniqueIdHashes(addedItem, true);
await ExecuteUIThread(() => { itemViewModel.MailCopy = addedItem; });
shouldExit = true;
}
}
}
}
if (!shouldExit)
{
// At this point all items are already checked and not suitable option was available.
// Item doesn't belong to any thread.
// Just add it to the collection.
var groupKey = GetGroupingKey(addedItem);
await ExecuteUIThread(() => { InsertItemInternal(groupKey, addedItem); });
}
_mailItemSource.InsertItem(groupKey, listComparer, new ThreadMailItemViewModel(threadMailItem), listComparer.GetItemComparer());
}
public void AddRange(IEnumerable<IMailItem> items, bool clearIdCache)
else
{
if (clearIdCache)
{
MailCopyIdHashSet.Clear();
}
var groupedByName = items
.GroupBy(a => GetGroupingKey(a))
.Select(a => new ObservableGroup<object, IMailItem>(a.Key, a));
foreach (var group in groupedByName)
{
// Store all mail copy ids for faster access.
foreach (var item in group)
{
if (item is MailItemViewModel mailCopyItem && !MailCopyIdHashSet.Contains(item.UniqueId))
{
MailCopyIdHashSet.Add(item.UniqueId);
}
else if (item is ThreadMailItemViewModel threadMailItem)
{
foreach (var mailItem in threadMailItem.ThreadItems)
{
if (!MailCopyIdHashSet.Contains(mailItem.UniqueId))
{
MailCopyIdHashSet.Add(mailItem.UniqueId);
}
}
}
}
var existingGroup = _mailItemSource.FirstGroupByKeyOrDefault(group.Key);
if (existingGroup == null)
{
_mailItemSource.AddGroup(group.Key, group);
}
else
{
foreach (var item in group)
{
existingGroup.Add(item);
}
}
}
_mailItemSource.InsertItem(groupKey, listComparer, mailItem, listComparer.GetItemComparer());
}
}
public MailItemContainer GetMailItemContainer(Guid uniqueMailId)
private void RemoveItemInternal(ObservableGroup<object, IMailItem> group, IMailItem mailItem)
{
UpdateUniqueIdHashes(mailItem, false);
MailItemRemoved?.Invoke(this, mailItem);
group.Remove(mailItem);
if (group.Count == 0)
{
var groupCount = _mailItemSource.Count;
for (int i = 0; i < groupCount; i++)
{
var group = _mailItemSource[i];
for (int k = 0; k < group.Count; k++)
{
var item = group[k];
if (item is MailItemViewModel singleMailItemViewModel && singleMailItemViewModel.UniqueId == uniqueMailId)
return new MailItemContainer(singleMailItemViewModel);
else if (item is ThreadMailItemViewModel threadMailItemViewModel && threadMailItemViewModel.HasUniqueId(uniqueMailId))
{
var singleItemViewModel = threadMailItemViewModel.GetItemById(uniqueMailId) as MailItemViewModel;
return new MailItemContainer(singleItemViewModel, threadMailItemViewModel);
}
}
}
return null;
_mailItemSource.RemoveGroup(group.Key);
}
}
/// <summary>
/// Fins the item container that updated mail copy belongs to and updates it.
/// </summary>
/// <param name="updatedMailCopy">Updated mail copy.</param>
/// <returns></returns>
public async Task UpdateMailCopy(MailCopy updatedMailCopy)
public async Task AddAsync(MailCopy addedItem)
{
// Check all items for whether this item should be threaded with them.
bool shouldExit = false;
var groupCount = _mailItemSource.Count;
var addedAccountProviderType = addedItem.AssignedAccount.ProviderType;
var threadingStrategy = ThreadingStrategyProvider?.GetStrategy(addedAccountProviderType);
for (int i = 0; i < groupCount; i++)
{
// This item doesn't exist in the list.
if (!MailCopyIdHashSet.Contains(updatedMailCopy.UniqueId))
if (shouldExit) break;
var group = _mailItemSource[i];
for (int k = 0; k < group.Count; k++)
{
return;
}
var item = group[k];
await ExecuteUIThread(() =>
{
var itemContainer = GetMailItemContainer(updatedMailCopy.UniqueId);
if (itemContainer == null) return;
if (itemContainer.ItemViewModel != null)
if (threadingStrategy?.ShouldThreadWithItem(addedItem, item) ?? false)
{
UpdateUniqueIdHashes(itemContainer.ItemViewModel, false);
}
shouldExit = true;
if (itemContainer.ItemViewModel != null)
{
itemContainer.ItemViewModel.MailCopy = updatedMailCopy;
}
UpdateUniqueIdHashes(updatedMailCopy, true);
// Call thread notifications if possible.
itemContainer.ThreadViewModel?.NotifyPropertyChanges();
});
}
public MailItemViewModel GetNextItem(MailCopy mailCopy)
{
var groupCount = _mailItemSource.Count;
for (int i = 0; i < groupCount; i++)
{
var group = _mailItemSource[i];
for (int k = 0; k < group.Count; k++)
{
var item = group[k];
if (item is MailItemViewModel singleMailItemViewModel && singleMailItemViewModel.UniqueId == mailCopy.UniqueId)
if (item is ThreadMailItemViewModel threadMailItemViewModel)
{
if (k + 1 < group.Count)
{
return group[k + 1] as MailItemViewModel;
}
else if (i + 1 < groupCount)
{
return _mailItemSource[i + 1][0] as MailItemViewModel;
}
else
{
return null;
}
}
else if (item is ThreadMailItemViewModel threadMailItemViewModel && threadMailItemViewModel.HasUniqueId(mailCopy.UniqueId))
{
var singleItemViewModel = threadMailItemViewModel.GetItemById(mailCopy.UniqueId) as MailItemViewModel;
// Item belongs to existing thread.
if (singleItemViewModel == null) return null;
var singleItemIndex = threadMailItemViewModel.ThreadItems.IndexOf(singleItemViewModel);
if (singleItemIndex + 1 < threadMailItemViewModel.ThreadItems.Count)
{
return threadMailItemViewModel.ThreadItems[singleItemIndex + 1] as MailItemViewModel;
}
else if (i + 1 < groupCount)
{
return _mailItemSource[i + 1][0] as MailItemViewModel;
}
else
{
return null;
}
}
}
}
return null;
}
public async Task RemoveAsync(MailCopy removeItem)
{
// This item doesn't exist in the list.
if (!MailCopyIdHashSet.Contains(removeItem.UniqueId)) return;
// Check all items for whether this item should be threaded with them.
bool shouldExit = false;
var groupCount = _mailItemSource.Count;
for (int i = 0; i < groupCount; i++)
{
if (shouldExit) break;
var group = _mailItemSource[i];
for (int k = 0; k < group.Count; k++)
{
var item = group[k];
if (item is ThreadMailItemViewModel threadMailItemViewModel && threadMailItemViewModel.HasUniqueId(removeItem.UniqueId))
{
var removalItem = threadMailItemViewModel.GetItemById(removeItem.UniqueId);
if (removalItem == null) return;
// Threads' Id is equal to the last item they hold.
// We can't do Id check here because that'd remove the whole thread.
/* Remove item from the thread.
* If thread had 1 item inside:
* -> Remove the thread and insert item as single item.
* If thread had 0 item inside:
* -> Remove the thread.
/* Add original item to the thread.
* If new group key is not the same as existing thread:
* -> Remove the whole thread from list
* -> Add the thread to the list again for sorting.
* Update thread properties.
*/
var oldGroupKey = GetGroupingKey(threadMailItemViewModel);
var existingGroupKey = GetGroupingKey(threadMailItemViewModel);
await ExecuteUIThread(() => { threadMailItemViewModel.RemoveCopyItem(removalItem); });
await ExecuteUIThread(() => { threadMailItemViewModel.AddMailItemViewModel(addedItem); });
if (threadMailItemViewModel.ThreadItems.Count == 1)
var newGroupKey = GetGroupingKey(threadMailItemViewModel);
if (!existingGroupKey.Equals(newGroupKey))
{
// Convert to single item.
var singleViewModel = threadMailItemViewModel.GetSingleItemViewModel();
var groupKey = GetGroupingKey(singleViewModel);
var mailThreadItems = threadMailItemViewModel.GetThreadMailItem();
await ExecuteUIThread(() =>
{
// Group must be changed for this thread.
// Remove the thread first.
RemoveItemInternal(group, threadMailItemViewModel);
InsertItemInternal(groupKey, singleViewModel);
// Insert new view model because the previous one might've been deleted with the group.
InsertItemInternal(newGroupKey, new ThreadMailItemViewModel(mailThreadItems));
});
// If thread->single conversion is being done, we should ignore it for non-draft items.
// eg. Deleting a reply message from draft folder. Single non-draft item should not be re-added.
if (PruneSingleNonDraftItems && !singleViewModel.IsDraft)
{
// This item should not be here anymore.
// It's basically a reply mail in Draft folder.
var newGroup = _mailItemSource.FirstGroupByKeyOrDefault(groupKey);
if (newGroup != null)
{
await ExecuteUIThread(() => { RemoveItemInternal(newGroup, singleViewModel); });
}
}
}
else if (threadMailItemViewModel.ThreadItems.Count == 0)
{
await ExecuteUIThread(() => { RemoveItemInternal(group, threadMailItemViewModel); });
}
else
{
// Item inside the thread is removed.
await ExecuteUIThread(() => { threadMailItemViewModel.ThreadItems.Remove(removalItem); });
UpdateUniqueIdHashes(removalItem, false);
await ExecuteUIThread(() => { threadMailItemViewModel.NotifyPropertyChanges(); });
}
shouldExit = true;
UpdateUniqueIdHashes(addedItem, true);
break;
}
else if (item.UniqueId == removeItem.UniqueId)
else
{
await ExecuteUIThread(() => { RemoveItemInternal(group, item); });
// Item belongs to a single mail item that is not threaded yet.
// Same item might've been tried to added as well.
// In that case we must just update the item but not thread it.
shouldExit = true;
/* Remove target item.
* Create a new thread with both items.
* Add new thread to the list.
*/
if (item.Id == addedItem.Id)
{
// Item is already added to the list.
// We need to update the copy it holds.
if (item is MailItemViewModel itemViewModel)
{
await ExecuteUIThread(() => { itemViewModel.MailCopy = addedItem; });
UpdateUniqueIdHashes(itemViewModel, false);
UpdateUniqueIdHashes(addedItem, true);
}
}
else
{
// Single item that must be threaded together with added item.
var threadMailItem = new ThreadMailItem();
await ExecuteUIThread(() =>
{
threadMailItem.AddThreadItem(item);
threadMailItem.AddThreadItem(addedItem);
if (threadMailItem.ThreadItems.Count == 1) return;
var newGroupKey = GetGroupingKey(threadMailItem);
RemoveItemInternal(group, item);
InsertItemInternal(newGroupKey, threadMailItem);
});
}
break;
}
}
else
{
// Update properties.
if (item.Id == addedItem.Id && item is MailItemViewModel itemViewModel)
{
UpdateUniqueIdHashes(itemViewModel, false);
UpdateUniqueIdHashes(addedItem, true);
await ExecuteUIThread(() => { itemViewModel.MailCopy = addedItem; });
shouldExit = true;
}
}
}
}
private async Task ExecuteUIThread(Action action) => await CoreDispatcher?.ExecuteOnUIThread(action);
if (!shouldExit)
{
// At this point all items are already checked and not suitable option was available.
// Item doesn't belong to any thread.
// Just add it to the collection.
var groupKey = GetGroupingKey(addedItem);
await ExecuteUIThread(() => { InsertItemInternal(groupKey, addedItem); });
}
}
public void AddRange(IEnumerable<IMailItem> items, bool clearIdCache)
{
if (clearIdCache)
{
MailCopyIdHashSet.Clear();
}
var groupedByName = items
.GroupBy(a => GetGroupingKey(a))
.Select(a => new ObservableGroup<object, IMailItem>(a.Key, a));
foreach (var group in groupedByName)
{
// Store all mail copy ids for faster access.
foreach (var item in group)
{
if (item is MailItemViewModel mailCopyItem && !MailCopyIdHashSet.Contains(item.UniqueId))
{
MailCopyIdHashSet.Add(item.UniqueId);
}
else if (item is ThreadMailItemViewModel threadMailItem)
{
foreach (var mailItem in threadMailItem.ThreadItems)
{
if (!MailCopyIdHashSet.Contains(mailItem.UniqueId))
{
MailCopyIdHashSet.Add(mailItem.UniqueId);
}
}
}
}
var existingGroup = _mailItemSource.FirstGroupByKeyOrDefault(group.Key);
if (existingGroup == null)
{
_mailItemSource.AddGroup(group.Key, group);
}
else
{
foreach (var item in group)
{
existingGroup.Add(item);
}
}
}
}
public MailItemContainer GetMailItemContainer(Guid uniqueMailId)
{
var groupCount = _mailItemSource.Count;
for (int i = 0; i < groupCount; i++)
{
var group = _mailItemSource[i];
for (int k = 0; k < group.Count; k++)
{
var item = group[k];
if (item is MailItemViewModel singleMailItemViewModel && singleMailItemViewModel.UniqueId == uniqueMailId)
return new MailItemContainer(singleMailItemViewModel);
else if (item is ThreadMailItemViewModel threadMailItemViewModel && threadMailItemViewModel.HasUniqueId(uniqueMailId))
{
var singleItemViewModel = threadMailItemViewModel.GetItemById(uniqueMailId) as MailItemViewModel;
return new MailItemContainer(singleItemViewModel, threadMailItemViewModel);
}
}
}
return null;
}
/// <summary>
/// Fins the item container that updated mail copy belongs to and updates it.
/// </summary>
/// <param name="updatedMailCopy">Updated mail copy.</param>
/// <returns></returns>
public async Task UpdateMailCopy(MailCopy updatedMailCopy)
{
// This item doesn't exist in the list.
if (!MailCopyIdHashSet.Contains(updatedMailCopy.UniqueId))
{
return;
}
await ExecuteUIThread(() =>
{
var itemContainer = GetMailItemContainer(updatedMailCopy.UniqueId);
if (itemContainer == null) return;
if (itemContainer.ItemViewModel != null)
{
UpdateUniqueIdHashes(itemContainer.ItemViewModel, false);
}
if (itemContainer.ItemViewModel != null)
{
itemContainer.ItemViewModel.MailCopy = updatedMailCopy;
}
UpdateUniqueIdHashes(updatedMailCopy, true);
// Call thread notifications if possible.
itemContainer.ThreadViewModel?.NotifyPropertyChanges();
});
}
public MailItemViewModel GetNextItem(MailCopy mailCopy)
{
var groupCount = _mailItemSource.Count;
for (int i = 0; i < groupCount; i++)
{
var group = _mailItemSource[i];
for (int k = 0; k < group.Count; k++)
{
var item = group[k];
if (item is MailItemViewModel singleMailItemViewModel && singleMailItemViewModel.UniqueId == mailCopy.UniqueId)
{
if (k + 1 < group.Count)
{
return group[k + 1] as MailItemViewModel;
}
else if (i + 1 < groupCount)
{
return _mailItemSource[i + 1][0] as MailItemViewModel;
}
else
{
return null;
}
}
else if (item is ThreadMailItemViewModel threadMailItemViewModel && threadMailItemViewModel.HasUniqueId(mailCopy.UniqueId))
{
var singleItemViewModel = threadMailItemViewModel.GetItemById(mailCopy.UniqueId) as MailItemViewModel;
if (singleItemViewModel == null) return null;
var singleItemIndex = threadMailItemViewModel.ThreadItems.IndexOf(singleItemViewModel);
if (singleItemIndex + 1 < threadMailItemViewModel.ThreadItems.Count)
{
return threadMailItemViewModel.ThreadItems[singleItemIndex + 1] as MailItemViewModel;
}
else if (i + 1 < groupCount)
{
return _mailItemSource[i + 1][0] as MailItemViewModel;
}
else
{
return null;
}
}
}
}
return null;
}
public async Task RemoveAsync(MailCopy removeItem)
{
// This item doesn't exist in the list.
if (!MailCopyIdHashSet.Contains(removeItem.UniqueId)) return;
// Check all items for whether this item should be threaded with them.
bool shouldExit = false;
var groupCount = _mailItemSource.Count;
for (int i = 0; i < groupCount; i++)
{
if (shouldExit) break;
var group = _mailItemSource[i];
for (int k = 0; k < group.Count; k++)
{
var item = group[k];
if (item is ThreadMailItemViewModel threadMailItemViewModel && threadMailItemViewModel.HasUniqueId(removeItem.UniqueId))
{
var removalItem = threadMailItemViewModel.GetItemById(removeItem.UniqueId);
if (removalItem == null) return;
// Threads' Id is equal to the last item they hold.
// We can't do Id check here because that'd remove the whole thread.
/* Remove item from the thread.
* If thread had 1 item inside:
* -> Remove the thread and insert item as single item.
* If thread had 0 item inside:
* -> Remove the thread.
*/
var oldGroupKey = GetGroupingKey(threadMailItemViewModel);
await ExecuteUIThread(() => { threadMailItemViewModel.RemoveCopyItem(removalItem); });
if (threadMailItemViewModel.ThreadItems.Count == 1)
{
// Convert to single item.
var singleViewModel = threadMailItemViewModel.GetSingleItemViewModel();
var groupKey = GetGroupingKey(singleViewModel);
await ExecuteUIThread(() =>
{
RemoveItemInternal(group, threadMailItemViewModel);
InsertItemInternal(groupKey, singleViewModel);
});
// If thread->single conversion is being done, we should ignore it for non-draft items.
// eg. Deleting a reply message from draft folder. Single non-draft item should not be re-added.
if (PruneSingleNonDraftItems && !singleViewModel.IsDraft)
{
// This item should not be here anymore.
// It's basically a reply mail in Draft folder.
var newGroup = _mailItemSource.FirstGroupByKeyOrDefault(groupKey);
if (newGroup != null)
{
await ExecuteUIThread(() => { RemoveItemInternal(newGroup, singleViewModel); });
}
}
}
else if (threadMailItemViewModel.ThreadItems.Count == 0)
{
await ExecuteUIThread(() => { RemoveItemInternal(group, threadMailItemViewModel); });
}
else
{
// Item inside the thread is removed.
await ExecuteUIThread(() => { threadMailItemViewModel.ThreadItems.Remove(removalItem); });
UpdateUniqueIdHashes(removalItem, false);
}
shouldExit = true;
break;
}
else if (item.UniqueId == removeItem.UniqueId)
{
await ExecuteUIThread(() => { RemoveItemInternal(group, item); });
shouldExit = true;
break;
}
}
}
}
private async Task ExecuteUIThread(Action action) => await CoreDispatcher?.ExecuteOnUIThread(action);
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,32 +2,31 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Wino.Core.Domain;
namespace Wino.Mail.ViewModels.Data
namespace Wino.Mail.ViewModels.Data;
[DebuggerDisplay("{FolderTitle}")]
public partial class FolderPivotViewModel : ObservableObject
{
[DebuggerDisplay("{FolderTitle}")]
public partial class FolderPivotViewModel : ObservableObject
public bool? IsFocused { get; set; }
public string FolderTitle { get; }
public bool ShouldDisplaySelectedItemCount => IsExtendedMode ? SelectedItemCount > 1 : SelectedItemCount > 0;
[ObservableProperty]
private bool isSelected;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ShouldDisplaySelectedItemCount))]
private int selectedItemCount;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ShouldDisplaySelectedItemCount))]
private bool isExtendedMode = true;
public FolderPivotViewModel(string folderName, bool? isFocused)
{
public bool? IsFocused { get; set; }
public string FolderTitle { get; }
IsFocused = isFocused;
public bool ShouldDisplaySelectedItemCount => IsExtendedMode ? SelectedItemCount > 1 : SelectedItemCount > 0;
[ObservableProperty]
private bool isSelected;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ShouldDisplaySelectedItemCount))]
private int selectedItemCount;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ShouldDisplaySelectedItemCount))]
private bool isExtendedMode = true;
public FolderPivotViewModel(string folderName, bool? isFocused)
{
IsFocused = isFocused;
FolderTitle = IsFocused == null ? folderName : (IsFocused == true ? Translator.Focused : Translator.Other);
}
FolderTitle = IsFocused == null ? folderName : (IsFocused == true ? Translator.Focused : Translator.Other);
}
}

View File

@@ -5,94 +5,93 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Common;
using Wino.Core.Extensions;
namespace Wino.Mail.ViewModels.Data
namespace Wino.Mail.ViewModels.Data;
public partial class MailAttachmentViewModel : ObservableObject
{
public partial class MailAttachmentViewModel : ObservableObject
private readonly MimePart _mimePart;
public MailAttachmentType AttachmentType { get; }
public string FileName { get; }
public string FilePath { get; set; }
public string ReadableSize { get; }
public byte[] Content { get; set; }
public IMimeContent MimeContent => _mimePart.Content;
/// <summary>
/// Gets or sets whether attachment is busy with opening or saving etc.
/// </summary>
[ObservableProperty]
private bool isBusy;
public MailAttachmentViewModel(MimePart mimePart)
{
private readonly MimePart _mimePart;
_mimePart = mimePart;
public MailAttachmentType AttachmentType { get; }
public string FileName { get; }
public string FilePath { get; set; }
public string ReadableSize { get; }
public byte[] Content { get; set; }
var memoryStream = new MemoryStream();
public IMimeContent MimeContent => _mimePart.Content;
using (memoryStream) mimePart.Content.DecodeTo(memoryStream);
/// <summary>
/// Gets or sets whether attachment is busy with opening or saving etc.
/// </summary>
[ObservableProperty]
private bool isBusy;
Content = memoryStream.ToArray();
public MailAttachmentViewModel(MimePart mimePart)
FileName = mimePart.FileName;
ReadableSize = ((long)Content.Length).GetBytesReadable();
var extension = Path.GetExtension(FileName);
AttachmentType = GetAttachmentType(extension);
}
public MailAttachmentViewModel(SharedFile sharedFile)
{
Content = sharedFile.Data;
FileName = sharedFile.FileName;
FilePath = sharedFile.FullFilePath;
ReadableSize = ((long)sharedFile.Data.Length).GetBytesReadable();
var extension = Path.GetExtension(FileName);
AttachmentType = GetAttachmentType(extension);
}
public MailAttachmentType GetAttachmentType(string mediaSubtype)
{
if (string.IsNullOrEmpty(mediaSubtype))
return MailAttachmentType.None;
switch (mediaSubtype.ToLower())
{
_mimePart = mimePart;
var memoryStream = new MemoryStream();
using (memoryStream) mimePart.Content.DecodeTo(memoryStream);
Content = memoryStream.ToArray();
FileName = mimePart.FileName;
ReadableSize = ((long)Content.Length).GetBytesReadable();
var extension = Path.GetExtension(FileName);
AttachmentType = GetAttachmentType(extension);
}
public MailAttachmentViewModel(SharedFile sharedFile)
{
Content = sharedFile.Data;
FileName = sharedFile.FileName;
FilePath = sharedFile.FullFilePath;
ReadableSize = ((long)sharedFile.Data.Length).GetBytesReadable();
var extension = Path.GetExtension(FileName);
AttachmentType = GetAttachmentType(extension);
}
public MailAttachmentType GetAttachmentType(string mediaSubtype)
{
if (string.IsNullOrEmpty(mediaSubtype))
return MailAttachmentType.None;
switch (mediaSubtype.ToLower())
{
case ".exe":
return MailAttachmentType.Executable;
case ".rar":
return MailAttachmentType.RarArchive;
case ".zip":
return MailAttachmentType.Archive;
case ".ogg":
case ".mp3":
case ".wav":
case ".aac":
case ".alac":
return MailAttachmentType.Audio;
case ".mp4":
case ".wmv":
case ".avi":
case ".flv":
return MailAttachmentType.Video;
case ".pdf":
return MailAttachmentType.PDF;
case ".htm":
case ".html":
return MailAttachmentType.HTML;
case ".png":
case ".jpg":
case ".jpeg":
case ".gif":
case ".jiff":
return MailAttachmentType.Image;
default:
return MailAttachmentType.Other;
}
case ".exe":
return MailAttachmentType.Executable;
case ".rar":
return MailAttachmentType.RarArchive;
case ".zip":
return MailAttachmentType.Archive;
case ".ogg":
case ".mp3":
case ".wav":
case ".aac":
case ".alac":
return MailAttachmentType.Audio;
case ".mp4":
case ".wmv":
case ".avi":
case ".flv":
return MailAttachmentType.Video;
case ".pdf":
return MailAttachmentType.PDF;
case ".htm":
case ".html":
return MailAttachmentType.HTML;
case ".png":
case ".jpg":
case ".jpeg":
case ".gif":
case ".jiff":
return MailAttachmentType.Image;
default:
return MailAttachmentType.Other;
}
}
}

View File

@@ -1,20 +1,19 @@
using System;
namespace Wino.Mail.ViewModels.Data
namespace Wino.Mail.ViewModels.Data;
public class MailItemContainer
{
public class MailItemContainer
public MailItemViewModel ItemViewModel { get; set; }
public ThreadMailItemViewModel ThreadViewModel { get; set; }
public MailItemContainer(MailItemViewModel itemViewModel, ThreadMailItemViewModel threadViewModel) : this(itemViewModel)
{
public MailItemViewModel ItemViewModel { get; set; }
public ThreadMailItemViewModel ThreadViewModel { get; set; }
ThreadViewModel = threadViewModel ?? throw new ArgumentNullException(nameof(threadViewModel));
}
public MailItemContainer(MailItemViewModel itemViewModel, ThreadMailItemViewModel threadViewModel) : this(itemViewModel)
{
ThreadViewModel = threadViewModel ?? throw new ArgumentNullException(nameof(threadViewModel));
}
public MailItemContainer(MailItemViewModel itemViewModel)
{
ItemViewModel = itemViewModel ?? throw new ArgumentNullException(nameof(itemViewModel));
}
public MailItemContainer(MailItemViewModel itemViewModel)
{
ItemViewModel = itemViewModel ?? throw new ArgumentNullException(nameof(itemViewModel));
}
}

View File

@@ -5,103 +5,102 @@ using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Models.MailItem;
namespace Wino.Mail.ViewModels.Data
namespace Wino.Mail.ViewModels.Data;
/// <summary>
/// Single view model for IMailItem representation.
/// </summary>
public partial class MailItemViewModel(MailCopy mailCopy) : ObservableObject, IMailItem
{
/// <summary>
/// Single view model for IMailItem representation.
/// </summary>
public partial class MailItemViewModel(MailCopy mailCopy) : ObservableObject, IMailItem
[ObservableProperty]
private MailCopy mailCopy = mailCopy;
public Guid UniqueId => ((IMailItem)MailCopy).UniqueId;
public string ThreadId => ((IMailItem)MailCopy).ThreadId;
public string MessageId => ((IMailItem)MailCopy).MessageId;
public DateTime CreationDate => ((IMailItem)MailCopy).CreationDate;
public string References => ((IMailItem)MailCopy).References;
public string InReplyTo => ((IMailItem)MailCopy).InReplyTo;
[ObservableProperty]
private bool isCustomFocused;
[ObservableProperty]
private bool isSelected;
public bool IsFlagged
{
[ObservableProperty]
private MailCopy mailCopy = mailCopy;
public Guid UniqueId => ((IMailItem)MailCopy).UniqueId;
public string ThreadId => ((IMailItem)MailCopy).ThreadId;
public string MessageId => ((IMailItem)MailCopy).MessageId;
public DateTime CreationDate => ((IMailItem)MailCopy).CreationDate;
public string References => ((IMailItem)MailCopy).References;
public string InReplyTo => ((IMailItem)MailCopy).InReplyTo;
[ObservableProperty]
private bool isCustomFocused;
[ObservableProperty]
private bool isSelected;
public bool IsFlagged
{
get => MailCopy.IsFlagged;
set => SetProperty(MailCopy.IsFlagged, value, MailCopy, (u, n) => u.IsFlagged = n);
}
public string FromName
{
get => string.IsNullOrEmpty(MailCopy.FromName) ? MailCopy.FromAddress : MailCopy.FromName;
set => SetProperty(MailCopy.FromName, value, MailCopy, (u, n) => u.FromName = n);
}
public bool IsFocused
{
get => MailCopy.IsFocused;
set => SetProperty(MailCopy.IsFocused, value, MailCopy, (u, n) => u.IsFocused = n);
}
public bool IsRead
{
get => MailCopy.IsRead;
set => SetProperty(MailCopy.IsRead, value, MailCopy, (u, n) => u.IsRead = n);
}
public bool IsDraft
{
get => MailCopy.IsDraft;
set => SetProperty(MailCopy.IsDraft, value, MailCopy, (u, n) => u.IsDraft = n);
}
public string DraftId
{
get => MailCopy.DraftId;
set => SetProperty(MailCopy.DraftId, value, MailCopy, (u, n) => u.DraftId = n);
}
public string Id
{
get => MailCopy.Id;
set => SetProperty(MailCopy.Id, value, MailCopy, (u, n) => u.Id = n);
}
public string Subject
{
get => MailCopy.Subject;
set => SetProperty(MailCopy.Subject, value, MailCopy, (u, n) => u.Subject = n);
}
public string PreviewText
{
get => MailCopy.PreviewText;
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;
public Guid FileId => ((IMailItem)MailCopy).FileId;
public AccountContact SenderContact => ((IMailItem)MailCopy).SenderContact;
public IEnumerable<Guid> GetContainingIds() => new[] { UniqueId };
get => MailCopy.IsFlagged;
set => SetProperty(MailCopy.IsFlagged, value, MailCopy, (u, n) => u.IsFlagged = n);
}
public string FromName
{
get => string.IsNullOrEmpty(MailCopy.FromName) ? MailCopy.FromAddress : MailCopy.FromName;
set => SetProperty(MailCopy.FromName, value, MailCopy, (u, n) => u.FromName = n);
}
public bool IsFocused
{
get => MailCopy.IsFocused;
set => SetProperty(MailCopy.IsFocused, value, MailCopy, (u, n) => u.IsFocused = n);
}
public bool IsRead
{
get => MailCopy.IsRead;
set => SetProperty(MailCopy.IsRead, value, MailCopy, (u, n) => u.IsRead = n);
}
public bool IsDraft
{
get => MailCopy.IsDraft;
set => SetProperty(MailCopy.IsDraft, value, MailCopy, (u, n) => u.IsDraft = n);
}
public string DraftId
{
get => MailCopy.DraftId;
set => SetProperty(MailCopy.DraftId, value, MailCopy, (u, n) => u.DraftId = n);
}
public string Id
{
get => MailCopy.Id;
set => SetProperty(MailCopy.Id, value, MailCopy, (u, n) => u.Id = n);
}
public string Subject
{
get => MailCopy.Subject;
set => SetProperty(MailCopy.Subject, value, MailCopy, (u, n) => u.Subject = n);
}
public string PreviewText
{
get => MailCopy.PreviewText;
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;
public Guid FileId => ((IMailItem)MailCopy).FileId;
public AccountContact SenderContact => ((IMailItem)MailCopy).SenderContact;
public IEnumerable<Guid> GetContainingIds() => new[] { UniqueId };
}

View File

@@ -8,120 +8,119 @@ using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Models.MailItem;
namespace Wino.Mail.ViewModels.Data
namespace Wino.Mail.ViewModels.Data;
/// <summary>
/// Thread mail item (multiple IMailItem) view model representation.
/// </summary>
public partial class ThreadMailItemViewModel : ObservableObject, IMailItemThread, IComparable<string>, IComparable<DateTime>
{
/// <summary>
/// Thread mail item (multiple IMailItem) view model representation.
/// </summary>
public partial class ThreadMailItemViewModel : ObservableObject, IMailItemThread, IComparable<string>, IComparable<DateTime>
public ObservableCollection<IMailItem> ThreadItems => (MailItem as IMailItemThread)?.ThreadItems ?? [];
public AccountContact SenderContact => ((IMailItemThread)MailItem).SenderContact;
[ObservableProperty]
private ThreadMailItem mailItem;
[ObservableProperty]
private bool isThreadExpanded;
public ThreadMailItemViewModel(ThreadMailItem threadMailItem)
{
public ObservableCollection<IMailItem> ThreadItems => (MailItem as IMailItemThread)?.ThreadItems ?? [];
public AccountContact SenderContact => ((IMailItemThread)MailItem).SenderContact;
MailItem = new ThreadMailItem();
[ObservableProperty]
private ThreadMailItem mailItem;
[ObservableProperty]
private bool isThreadExpanded;
public ThreadMailItemViewModel(ThreadMailItem threadMailItem)
// Local copies
foreach (var item in threadMailItem.ThreadItems)
{
MailItem = new ThreadMailItem();
// Local copies
foreach (var item in threadMailItem.ThreadItems)
{
AddMailItemViewModel(item);
}
AddMailItemViewModel(item);
}
public ThreadMailItem GetThreadMailItem() => MailItem;
public IEnumerable<MailCopy> GetMailCopies()
=> ThreadItems.OfType<MailItemViewModel>().Select(a => a.MailCopy);
public void AddMailItemViewModel(IMailItem mailItem)
{
if (mailItem == null) return;
if (mailItem is MailCopy mailCopy)
MailItem.AddThreadItem(new MailItemViewModel(mailCopy));
else if (mailItem is MailItemViewModel mailItemViewModel)
MailItem.AddThreadItem(mailItemViewModel);
else
Debugger.Break();
}
public bool HasUniqueId(Guid uniqueMailId)
=> ThreadItems.Any(a => a.UniqueId == uniqueMailId);
public IMailItem GetItemById(Guid uniqueMailId)
=> ThreadItems.FirstOrDefault(a => a.UniqueId == uniqueMailId);
public void RemoveCopyItem(IMailItem item)
{
MailCopy copyToRemove = null;
if (item is MailItemViewModel mailItemViewModel)
copyToRemove = mailItemViewModel.MailCopy;
else if (item is MailCopy copyItem)
copyToRemove = copyItem;
var existedItem = ThreadItems.FirstOrDefault(a => a.Id == copyToRemove.Id);
if (existedItem == null) return;
ThreadItems.Remove(existedItem);
NotifyPropertyChanges();
}
public void NotifyPropertyChanges()
{
// TODO
// Stupid temporary fix for not updating UI.
// This view model must be reworked with ThreadMailItem together.
var current = MailItem;
MailItem = null;
MailItem = current;
}
public IMailItem LatestMailItem => ((IMailItemThread)MailItem).LatestMailItem;
public IMailItem FirstMailItem => ((IMailItemThread)MailItem).FirstMailItem;
public string Id => ((IMailItem)MailItem).Id;
public string Subject => ((IMailItem)MailItem).Subject;
public string ThreadId => ((IMailItem)MailItem).ThreadId;
public string MessageId => ((IMailItem)MailItem).MessageId;
public string References => ((IMailItem)MailItem).References;
public string PreviewText => ((IMailItem)MailItem).PreviewText;
public string FromName => ((IMailItem)MailItem).FromName;
public DateTime CreationDate => ((IMailItem)MailItem).CreationDate;
public string FromAddress => ((IMailItem)MailItem).FromAddress;
public bool HasAttachments => ((IMailItem)MailItem).HasAttachments;
public bool IsFlagged => ((IMailItem)MailItem).IsFlagged;
public bool IsFocused => ((IMailItem)MailItem).IsFocused;
public bool IsRead => ((IMailItem)MailItem).IsRead;
public bool IsDraft => ((IMailItem)MailItem).IsDraft;
public string DraftId => string.Empty;
public string InReplyTo => ((IMailItem)MailItem).InReplyTo;
public MailItemFolder AssignedFolder => ((IMailItem)MailItem).AssignedFolder;
public MailAccount AssignedAccount => ((IMailItem)MailItem).AssignedAccount;
public Guid UniqueId => ((IMailItem)MailItem).UniqueId;
public Guid FileId => ((IMailItem)MailItem).FileId;
public int CompareTo(DateTime other) => CreationDate.CompareTo(other);
public int CompareTo(string other) => FromName.CompareTo(other);
// Get single mail item view model out of the only item in thread items.
public MailItemViewModel GetSingleItemViewModel() => ThreadItems.First() as MailItemViewModel;
public IEnumerable<Guid> GetContainingIds() => ((IMailItemThread)MailItem).GetContainingIds();
}
public ThreadMailItem GetThreadMailItem() => MailItem;
public IEnumerable<MailCopy> GetMailCopies()
=> ThreadItems.OfType<MailItemViewModel>().Select(a => a.MailCopy);
public void AddMailItemViewModel(IMailItem mailItem)
{
if (mailItem == null) return;
if (mailItem is MailCopy mailCopy)
MailItem.AddThreadItem(new MailItemViewModel(mailCopy));
else if (mailItem is MailItemViewModel mailItemViewModel)
MailItem.AddThreadItem(mailItemViewModel);
else
Debugger.Break();
}
public bool HasUniqueId(Guid uniqueMailId)
=> ThreadItems.Any(a => a.UniqueId == uniqueMailId);
public IMailItem GetItemById(Guid uniqueMailId)
=> ThreadItems.FirstOrDefault(a => a.UniqueId == uniqueMailId);
public void RemoveCopyItem(IMailItem item)
{
MailCopy copyToRemove = null;
if (item is MailItemViewModel mailItemViewModel)
copyToRemove = mailItemViewModel.MailCopy;
else if (item is MailCopy copyItem)
copyToRemove = copyItem;
var existedItem = ThreadItems.FirstOrDefault(a => a.Id == copyToRemove.Id);
if (existedItem == null) return;
ThreadItems.Remove(existedItem);
NotifyPropertyChanges();
}
public void NotifyPropertyChanges()
{
// TODO
// Stupid temporary fix for not updating UI.
// This view model must be reworked with ThreadMailItem together.
var current = MailItem;
MailItem = null;
MailItem = current;
}
public IMailItem LatestMailItem => ((IMailItemThread)MailItem).LatestMailItem;
public IMailItem FirstMailItem => ((IMailItemThread)MailItem).FirstMailItem;
public string Id => ((IMailItem)MailItem).Id;
public string Subject => ((IMailItem)MailItem).Subject;
public string ThreadId => ((IMailItem)MailItem).ThreadId;
public string MessageId => ((IMailItem)MailItem).MessageId;
public string References => ((IMailItem)MailItem).References;
public string PreviewText => ((IMailItem)MailItem).PreviewText;
public string FromName => ((IMailItem)MailItem).FromName;
public DateTime CreationDate => ((IMailItem)MailItem).CreationDate;
public string FromAddress => ((IMailItem)MailItem).FromAddress;
public bool HasAttachments => ((IMailItem)MailItem).HasAttachments;
public bool IsFlagged => ((IMailItem)MailItem).IsFlagged;
public bool IsFocused => ((IMailItem)MailItem).IsFocused;
public bool IsRead => ((IMailItem)MailItem).IsRead;
public bool IsDraft => ((IMailItem)MailItem).IsDraft;
public string DraftId => string.Empty;
public string InReplyTo => ((IMailItem)MailItem).InReplyTo;
public MailItemFolder AssignedFolder => ((IMailItem)MailItem).AssignedFolder;
public MailAccount AssignedAccount => ((IMailItem)MailItem).AssignedAccount;
public Guid UniqueId => ((IMailItem)MailItem).UniqueId;
public Guid FileId => ((IMailItem)MailItem).FileId;
public int CompareTo(DateTime other) => CreationDate.CompareTo(other);
public int CompareTo(string other) => FromName.CompareTo(other);
// Get single mail item view model out of the only item in thread items.
public MailItemViewModel GetSingleItemViewModel() => ThreadItems.First() as MailItemViewModel;
public IEnumerable<Guid> GetContainingIds() => ((IMailItemThread)MailItem).GetContainingIds();
}

View File

@@ -1,10 +1,9 @@
using Wino.Core.Domain.Interfaces;
using Wino.Core.ViewModels;
namespace Wino.Mail.ViewModels
namespace Wino.Mail.ViewModels;
public partial class IdlePageViewModel : CoreBaseViewModel
{
public partial class IdlePageViewModel : CoreBaseViewModel
{
public IdlePageViewModel(IMailDialogService dialogService) { }
}
public IdlePageViewModel(IMailDialogService dialogService) { }
}

View File

@@ -6,42 +6,41 @@ using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Domain.Models.Translations;
namespace Wino.Mail.ViewModels
namespace Wino.Mail.ViewModels;
public partial class LanguageTimePageViewModel(IPreferencesService preferencesService, ITranslationService translationService) : MailBaseViewModel
{
public partial class LanguageTimePageViewModel(IPreferencesService preferencesService, ITranslationService translationService) : MailBaseViewModel
public IPreferencesService PreferencesService { get; } = preferencesService;
private readonly ITranslationService _translationService = translationService;
[ObservableProperty]
private List<AppLanguageModel> _availableLanguages;
[ObservableProperty]
private AppLanguageModel _selectedLanguage;
private bool isInitialized = false;
public override void OnNavigatedTo(NavigationMode mode, object parameters)
{
public IPreferencesService PreferencesService { get; } = preferencesService;
private readonly ITranslationService _translationService = translationService;
base.OnNavigatedTo(mode, parameters);
[ObservableProperty]
private List<AppLanguageModel> _availableLanguages;
AvailableLanguages = _translationService.GetAvailableLanguages();
[ObservableProperty]
private AppLanguageModel _selectedLanguage;
SelectedLanguage = AvailableLanguages.FirstOrDefault(a => a.Language == PreferencesService.CurrentLanguage);
private bool isInitialized = false;
isInitialized = true;
}
public override void OnNavigatedTo(NavigationMode mode, object parameters)
protected override async void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (!isInitialized) return;
if (e.PropertyName == nameof(SelectedLanguage))
{
base.OnNavigatedTo(mode, parameters);
AvailableLanguages = _translationService.GetAvailableLanguages();
SelectedLanguage = AvailableLanguages.FirstOrDefault(a => a.Language == PreferencesService.CurrentLanguage);
isInitialized = true;
}
protected override async void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (!isInitialized) return;
if (e.PropertyName == nameof(SelectedLanguage))
{
await _translationService.InitializeLanguageAsync(SelectedLanguage.Language);
}
await _translationService.InitializeLanguageAsync(SelectedLanguage.Language);
}
}
}

View File

@@ -5,39 +5,38 @@ using Wino.Core.Domain.Models.Folders;
using Wino.Core.ViewModels;
using Wino.Messaging.UI;
namespace Wino.Mail.ViewModels
namespace Wino.Mail.ViewModels;
public class MailBaseViewModel : CoreBaseViewModel,
IRecipient<MailAddedMessage>,
IRecipient<MailRemovedMessage>,
IRecipient<MailUpdatedMessage>,
IRecipient<MailDownloadedMessage>,
IRecipient<DraftCreated>,
IRecipient<DraftFailed>,
IRecipient<DraftMapped>,
IRecipient<FolderRenamed>,
IRecipient<FolderSynchronizationEnabled>
{
public class MailBaseViewModel : CoreBaseViewModel,
IRecipient<MailAddedMessage>,
IRecipient<MailRemovedMessage>,
IRecipient<MailUpdatedMessage>,
IRecipient<MailDownloadedMessage>,
IRecipient<DraftCreated>,
IRecipient<DraftFailed>,
IRecipient<DraftMapped>,
IRecipient<FolderRenamed>,
IRecipient<FolderSynchronizationEnabled>
{
protected virtual void OnMailAdded(MailCopy addedMail) { }
protected virtual void OnMailRemoved(MailCopy removedMail) { }
protected virtual void OnMailUpdated(MailCopy updatedMail) { }
protected virtual void OnMailDownloaded(MailCopy downloadedMail) { }
protected virtual void OnDraftCreated(MailCopy draftMail, MailAccount account) { }
protected virtual void OnDraftFailed(MailCopy draftMail, MailAccount account) { }
protected virtual void OnDraftMapped(string localDraftCopyId, string remoteDraftCopyId) { }
protected virtual void OnFolderRenamed(IMailItemFolder mailItemFolder) { }
protected virtual void OnFolderSynchronizationEnabled(IMailItemFolder mailItemFolder) { }
protected virtual void OnMailAdded(MailCopy addedMail) { }
protected virtual void OnMailRemoved(MailCopy removedMail) { }
protected virtual void OnMailUpdated(MailCopy updatedMail) { }
protected virtual void OnMailDownloaded(MailCopy downloadedMail) { }
protected virtual void OnDraftCreated(MailCopy draftMail, MailAccount account) { }
protected virtual void OnDraftFailed(MailCopy draftMail, MailAccount account) { }
protected virtual void OnDraftMapped(string localDraftCopyId, string remoteDraftCopyId) { }
protected virtual void OnFolderRenamed(IMailItemFolder mailItemFolder) { }
protected virtual void OnFolderSynchronizationEnabled(IMailItemFolder mailItemFolder) { }
void IRecipient<MailAddedMessage>.Receive(MailAddedMessage message) => OnMailAdded(message.AddedMail);
void IRecipient<MailRemovedMessage>.Receive(MailRemovedMessage message) => OnMailRemoved(message.RemovedMail);
void IRecipient<MailUpdatedMessage>.Receive(MailUpdatedMessage message) => OnMailUpdated(message.UpdatedMail);
void IRecipient<MailDownloadedMessage>.Receive(MailDownloadedMessage message) => OnMailDownloaded(message.DownloadedMail);
void IRecipient<MailAddedMessage>.Receive(MailAddedMessage message) => OnMailAdded(message.AddedMail);
void IRecipient<MailRemovedMessage>.Receive(MailRemovedMessage message) => OnMailRemoved(message.RemovedMail);
void IRecipient<MailUpdatedMessage>.Receive(MailUpdatedMessage message) => OnMailUpdated(message.UpdatedMail);
void IRecipient<MailDownloadedMessage>.Receive(MailDownloadedMessage message) => OnMailDownloaded(message.DownloadedMail);
void IRecipient<DraftMapped>.Receive(DraftMapped message) => OnDraftMapped(message.LocalDraftCopyId, message.RemoteDraftCopyId);
void IRecipient<DraftFailed>.Receive(DraftFailed message) => OnDraftFailed(message.DraftMail, message.Account);
void IRecipient<DraftCreated>.Receive(DraftCreated message) => OnDraftCreated(message.DraftMail, message.Account);
void IRecipient<DraftMapped>.Receive(DraftMapped message) => OnDraftMapped(message.LocalDraftCopyId, message.RemoteDraftCopyId);
void IRecipient<DraftFailed>.Receive(DraftFailed message) => OnDraftFailed(message.DraftMail, message.Account);
void IRecipient<DraftCreated>.Receive(DraftCreated message) => OnDraftCreated(message.DraftMail, message.Account);
void IRecipient<FolderRenamed>.Receive(FolderRenamed message) => OnFolderRenamed(message.MailItemFolder);
void IRecipient<FolderSynchronizationEnabled>.Receive(FolderSynchronizationEnabled message) => OnFolderSynchronizationEnabled(message.MailItemFolder);
}
void IRecipient<FolderRenamed>.Receive(FolderRenamed message) => OnFolderRenamed(message.MailItemFolder);
void IRecipient<FolderSynchronizationEnabled>.Receive(FolderSynchronizationEnabled message) => OnFolderSynchronizationEnabled(message.MailItemFolder);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,13 @@
using Microsoft.Extensions.DependencyInjection;
using Wino.Core;
namespace Wino.Mail.ViewModels
namespace Wino.Mail.ViewModels;
public static class MailViewModelsContainerSetup
{
public static class MailViewModelsContainerSetup
public static void RegisterViewModelService(this IServiceCollection services)
{
public static void RegisterViewModelService(this IServiceCollection services)
{
// View models use core services.
services.RegisterCoreServices();
}
// View models use core services.
services.RegisterCoreServices();
}
}

View File

@@ -13,208 +13,207 @@ using Wino.Mail.ViewModels.Data;
using Wino.Messaging.Client.Navigation;
using Wino.Messaging.UI;
namespace Wino.Mail.ViewModels
namespace Wino.Mail.ViewModels;
public partial class MergedAccountDetailsPageViewModel : MailBaseViewModel,
IRecipient<MergedInboxRenamed>
{
public partial class MergedAccountDetailsPageViewModel : MailBaseViewModel,
IRecipient<MergedInboxRenamed>
[ObservableProperty]
private MergedAccountProviderDetailViewModel editingMergedAccount;
[ObservableProperty]
private string mergedAccountName;
public ObservableCollection<AccountProviderDetailViewModel> LinkedAccounts { get; set; } = [];
public ObservableCollection<AccountProviderDetailViewModel> UnlinkedAccounts { get; set; } = [];
// Empty Guid is passed for new created merged inboxes.
public bool IsMergedInboxSaved => EditingMergedAccount != null && EditingMergedAccount.MergedInbox.Id != Guid.Empty;
public bool CanUnlink => IsMergedInboxSaved;
// There must be at least 2 accounts linked to a merged account for link to exist.
public bool ShouldDeleteMergedAccount => LinkedAccounts.Count < 2;
public bool CanSaveChanges
{
[ObservableProperty]
private MergedAccountProviderDetailViewModel editingMergedAccount;
[ObservableProperty]
private string mergedAccountName;
public ObservableCollection<AccountProviderDetailViewModel> LinkedAccounts { get; set; } = [];
public ObservableCollection<AccountProviderDetailViewModel> UnlinkedAccounts { get; set; } = [];
// Empty Guid is passed for new created merged inboxes.
public bool IsMergedInboxSaved => EditingMergedAccount != null && EditingMergedAccount.MergedInbox.Id != Guid.Empty;
public bool CanUnlink => IsMergedInboxSaved;
// There must be at least 2 accounts linked to a merged account for link to exist.
public bool ShouldDeleteMergedAccount => LinkedAccounts.Count < 2;
public bool CanSaveChanges
get
{
get
{
if (IsMergedInboxSaved)
{
return ShouldDeleteMergedAccount || IsEditingAccountsDirty();
}
else
{
return LinkedAccounts.Any();
}
}
}
private readonly IMailDialogService _dialogService;
private readonly IAccountService _accountService;
private readonly IPreferencesService _preferencesService;
private readonly IProviderService _providerService;
public MergedAccountDetailsPageViewModel(IMailDialogService dialogService,
IAccountService accountService,
IPreferencesService preferencesService,
IProviderService providerService)
{
_dialogService = dialogService;
_accountService = accountService;
_preferencesService = preferencesService;
_providerService = providerService;
}
[RelayCommand(CanExecute = nameof(CanUnlink))]
private async Task UnlinkAccountsAsync()
{
if (EditingMergedAccount == null) return;
var isConfirmed = await _dialogService.ShowConfirmationDialogAsync(Translator.DialogMessage_UnlinkAccountsConfirmationMessage, Translator.DialogMessage_UnlinkAccountsConfirmationTitle, Translator.Buttons_Yes);
if (!isConfirmed) return;
await _accountService.UnlinkMergedInboxAsync(EditingMergedAccount.MergedInbox.Id);
Messenger.Send(new BackBreadcrumNavigationRequested());
}
[RelayCommand(CanExecute = nameof(CanSaveChanges))]
private async Task SaveChangesAsync()
{
if (ShouldDeleteMergedAccount)
{
await UnlinkAccountsAsync();
}
else
{
if (IsMergedInboxSaved)
{
await _accountService.UpdateMergedInboxAsync(EditingMergedAccount.MergedInbox.Id, LinkedAccounts.Select(a => a.Account.Id).ToList());
}
else
{
await _accountService.CreateMergeAccountsAsync(EditingMergedAccount.MergedInbox, LinkedAccounts.Select(a => a.Account).ToList());
}
// Startup entity is linked now. Change the startup entity.
if (_preferencesService.StartupEntityId != null && LinkedAccounts.Any(a => a.StartupEntityId == _preferencesService.StartupEntityId))
{
_preferencesService.StartupEntityId = EditingMergedAccount.MergedInbox.Id;
}
}
Messenger.Send(new BackBreadcrumNavigationRequested());
}
[RelayCommand]
private async Task RenameLinkAsync()
{
if (EditingMergedAccount == null) return;
var newName = await _dialogService.ShowTextInputDialogAsync(EditingMergedAccount.MergedInbox.Name,
Translator.DialogMessage_RenameLinkedAccountsTitle,
Translator.DialogMessage_RenameLinkedAccountsMessage,
Translator.FolderOperation_Rename);
if (string.IsNullOrWhiteSpace(newName)) return;
EditingMergedAccount.MergedInbox.Name = newName;
// Update database record as well.
if (IsMergedInboxSaved)
{
await _accountService.RenameMergedAccountAsync(EditingMergedAccount.MergedInbox.Id, newName);
return ShouldDeleteMergedAccount || IsEditingAccountsDirty();
}
else
{
// Publish the message manually since the merged inbox is not saved yet.
// This is only for breadcrump item update.
Messenger.Send(new MergedInboxRenamed(EditingMergedAccount.MergedInbox.Id, newName));
}
}
[RelayCommand]
private void LinkAccount(AccountProviderDetailViewModel account)
{
LinkedAccounts.Add(account);
UnlinkedAccounts.Remove(account);
}
[RelayCommand]
private void UnlinkAccount(AccountProviderDetailViewModel account)
{
UnlinkedAccounts.Add(account);
LinkedAccounts.Remove(account);
}
private bool IsEditingAccountsDirty()
{
if (EditingMergedAccount == null) return false;
return EditingMergedAccount.HoldingAccounts.Count != LinkedAccounts.Count ||
EditingMergedAccount.HoldingAccounts.Any(a => !LinkedAccounts.Any(la => la.Account.Id == a.Account.Id));
}
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
{
base.OnNavigatedFrom(mode, parameters);
LinkedAccounts.CollectionChanged -= LinkedAccountsUpdated;
}
private void LinkedAccountsUpdated(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(nameof(ShouldDeleteMergedAccount));
SaveChangesCommand.NotifyCanExecuteChanged();
// TODO: Preview common folders for all linked accounts.
// Basically showing a preview of how menu items will look.
}
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
{
base.OnNavigatedTo(mode, parameters);
LinkedAccounts.CollectionChanged -= LinkedAccountsUpdated;
LinkedAccounts.CollectionChanged += LinkedAccountsUpdated;
if (parameters is MergedAccountProviderDetailViewModel editingMergedAccount)
{
MergedAccountName = editingMergedAccount.MergedInbox.Name;
EditingMergedAccount = editingMergedAccount;
foreach (var account in editingMergedAccount.HoldingAccounts)
{
LinkedAccounts.Add(account);
}
// Load unlinked accounts.
var allAccounts = await _accountService.GetAccountsAsync();
foreach (var account in allAccounts)
{
if (!LinkedAccounts.Any(a => a.Account.Id == account.Id))
{
var provider = _providerService.GetProviderDetail(account.ProviderType);
UnlinkedAccounts.Add(new AccountProviderDetailViewModel(provider, account));
}
}
}
UnlinkAccountsCommand.NotifyCanExecuteChanged();
}
public void Receive(MergedInboxRenamed message)
{
if (EditingMergedAccount?.MergedInbox.Id == message.MergedInboxId)
{
EditingMergedAccount.MergedInbox.Name = message.NewName;
return LinkedAccounts.Any();
}
}
}
private readonly IMailDialogService _dialogService;
private readonly IAccountService _accountService;
private readonly IPreferencesService _preferencesService;
private readonly IProviderService _providerService;
public MergedAccountDetailsPageViewModel(IMailDialogService dialogService,
IAccountService accountService,
IPreferencesService preferencesService,
IProviderService providerService)
{
_dialogService = dialogService;
_accountService = accountService;
_preferencesService = preferencesService;
_providerService = providerService;
}
[RelayCommand(CanExecute = nameof(CanUnlink))]
private async Task UnlinkAccountsAsync()
{
if (EditingMergedAccount == null) return;
var isConfirmed = await _dialogService.ShowConfirmationDialogAsync(Translator.DialogMessage_UnlinkAccountsConfirmationMessage, Translator.DialogMessage_UnlinkAccountsConfirmationTitle, Translator.Buttons_Yes);
if (!isConfirmed) return;
await _accountService.UnlinkMergedInboxAsync(EditingMergedAccount.MergedInbox.Id);
Messenger.Send(new BackBreadcrumNavigationRequested());
}
[RelayCommand(CanExecute = nameof(CanSaveChanges))]
private async Task SaveChangesAsync()
{
if (ShouldDeleteMergedAccount)
{
await UnlinkAccountsAsync();
}
else
{
if (IsMergedInboxSaved)
{
await _accountService.UpdateMergedInboxAsync(EditingMergedAccount.MergedInbox.Id, LinkedAccounts.Select(a => a.Account.Id).ToList());
}
else
{
await _accountService.CreateMergeAccountsAsync(EditingMergedAccount.MergedInbox, LinkedAccounts.Select(a => a.Account).ToList());
}
// Startup entity is linked now. Change the startup entity.
if (_preferencesService.StartupEntityId != null && LinkedAccounts.Any(a => a.StartupEntityId == _preferencesService.StartupEntityId))
{
_preferencesService.StartupEntityId = EditingMergedAccount.MergedInbox.Id;
}
}
Messenger.Send(new BackBreadcrumNavigationRequested());
}
[RelayCommand]
private async Task RenameLinkAsync()
{
if (EditingMergedAccount == null) return;
var newName = await _dialogService.ShowTextInputDialogAsync(EditingMergedAccount.MergedInbox.Name,
Translator.DialogMessage_RenameLinkedAccountsTitle,
Translator.DialogMessage_RenameLinkedAccountsMessage,
Translator.FolderOperation_Rename);
if (string.IsNullOrWhiteSpace(newName)) return;
EditingMergedAccount.MergedInbox.Name = newName;
// Update database record as well.
if (IsMergedInboxSaved)
{
await _accountService.RenameMergedAccountAsync(EditingMergedAccount.MergedInbox.Id, newName);
}
else
{
// Publish the message manually since the merged inbox is not saved yet.
// This is only for breadcrump item update.
Messenger.Send(new MergedInboxRenamed(EditingMergedAccount.MergedInbox.Id, newName));
}
}
[RelayCommand]
private void LinkAccount(AccountProviderDetailViewModel account)
{
LinkedAccounts.Add(account);
UnlinkedAccounts.Remove(account);
}
[RelayCommand]
private void UnlinkAccount(AccountProviderDetailViewModel account)
{
UnlinkedAccounts.Add(account);
LinkedAccounts.Remove(account);
}
private bool IsEditingAccountsDirty()
{
if (EditingMergedAccount == null) return false;
return EditingMergedAccount.HoldingAccounts.Count != LinkedAccounts.Count ||
EditingMergedAccount.HoldingAccounts.Any(a => !LinkedAccounts.Any(la => la.Account.Id == a.Account.Id));
}
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
{
base.OnNavigatedFrom(mode, parameters);
LinkedAccounts.CollectionChanged -= LinkedAccountsUpdated;
}
private void LinkedAccountsUpdated(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(nameof(ShouldDeleteMergedAccount));
SaveChangesCommand.NotifyCanExecuteChanged();
// TODO: Preview common folders for all linked accounts.
// Basically showing a preview of how menu items will look.
}
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
{
base.OnNavigatedTo(mode, parameters);
LinkedAccounts.CollectionChanged -= LinkedAccountsUpdated;
LinkedAccounts.CollectionChanged += LinkedAccountsUpdated;
if (parameters is MergedAccountProviderDetailViewModel editingMergedAccount)
{
MergedAccountName = editingMergedAccount.MergedInbox.Name;
EditingMergedAccount = editingMergedAccount;
foreach (var account in editingMergedAccount.HoldingAccounts)
{
LinkedAccounts.Add(account);
}
// Load unlinked accounts.
var allAccounts = await _accountService.GetAccountsAsync();
foreach (var account in allAccounts)
{
if (!LinkedAccounts.Any(a => a.Account.Id == account.Id))
{
var provider = _providerService.GetProviderDetail(account.ProviderType);
UnlinkedAccounts.Add(new AccountProviderDetailViewModel(provider, account));
}
}
}
UnlinkAccountsCommand.NotifyCanExecuteChanged();
}
public void Receive(MergedInboxRenamed message)
{
if (EditingMergedAccount?.MergedInbox.Id == message.MergedInboxId)
{
EditingMergedAccount.MergedInbox.Name = message.NewName;
}
}
}

View File

@@ -4,104 +4,103 @@ using Wino.Core.Domain;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
namespace Wino.Mail.ViewModels
namespace Wino.Mail.ViewModels;
public class MessageListPageViewModel : MailBaseViewModel
{
public class MessageListPageViewModel : MailBaseViewModel
public IPreferencesService PreferencesService { get; }
private int selectedMarkAsOptionIndex;
public int SelectedMarkAsOptionIndex
{
public IPreferencesService PreferencesService { get; }
private int selectedMarkAsOptionIndex;
public int SelectedMarkAsOptionIndex
get => selectedMarkAsOptionIndex;
set
{
get => selectedMarkAsOptionIndex;
set
if (SetProperty(ref selectedMarkAsOptionIndex, value))
{
if (SetProperty(ref selectedMarkAsOptionIndex, value))
if (value >= 0)
{
if (value >= 0)
{
PreferencesService.MarkAsPreference = (MailMarkAsOption)Enum.GetValues<MailMarkAsOption>().GetValue(value);
}
PreferencesService.MarkAsPreference = (MailMarkAsOption)Enum.GetValues<MailMarkAsOption>().GetValue(value);
}
}
}
private readonly List<MailOperation> availableHoverActions =
[
MailOperation.Archive,
MailOperation.SoftDelete,
MailOperation.SetFlag,
MailOperation.MarkAsRead,
MailOperation.MoveToJunk
];
public List<string> AvailableHoverActionsTranslations { get; set; } =
[
Translator.HoverActionOption_Archive,
Translator.HoverActionOption_Delete,
Translator.HoverActionOption_ToggleFlag,
Translator.HoverActionOption_ToggleRead,
Translator.HoverActionOption_MoveJunk
];
#region Properties
private int leftHoverActionIndex;
public int LeftHoverActionIndex
{
get => leftHoverActionIndex;
set
{
if (SetProperty(ref leftHoverActionIndex, value))
{
PreferencesService.LeftHoverAction = availableHoverActions[value];
}
}
}
private int centerHoverActionIndex;
public int CenterHoverActionIndex
{
get => centerHoverActionIndex;
set
{
if (SetProperty(ref centerHoverActionIndex, value))
{
PreferencesService.CenterHoverAction = availableHoverActions[value];
}
}
}
private int rightHoverActionIndex;
public int RightHoverActionIndex
{
get => rightHoverActionIndex;
set
{
if (SetProperty(ref rightHoverActionIndex, value))
{
PreferencesService.RightHoverAction = availableHoverActions[value];
}
}
}
#endregion
public MessageListPageViewModel(IMailDialogService dialogService,
IPreferencesService preferencesService)
{
PreferencesService = preferencesService;
leftHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.LeftHoverAction);
centerHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.CenterHoverAction);
rightHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.RightHoverAction);
SelectedMarkAsOptionIndex = Array.IndexOf(Enum.GetValues<MailMarkAsOption>(), PreferencesService.MarkAsPreference);
}
}
private readonly List<MailOperation> availableHoverActions =
[
MailOperation.Archive,
MailOperation.SoftDelete,
MailOperation.SetFlag,
MailOperation.MarkAsRead,
MailOperation.MoveToJunk
];
public List<string> AvailableHoverActionsTranslations { get; set; } =
[
Translator.HoverActionOption_Archive,
Translator.HoverActionOption_Delete,
Translator.HoverActionOption_ToggleFlag,
Translator.HoverActionOption_ToggleRead,
Translator.HoverActionOption_MoveJunk
];
#region Properties
private int leftHoverActionIndex;
public int LeftHoverActionIndex
{
get => leftHoverActionIndex;
set
{
if (SetProperty(ref leftHoverActionIndex, value))
{
PreferencesService.LeftHoverAction = availableHoverActions[value];
}
}
}
private int centerHoverActionIndex;
public int CenterHoverActionIndex
{
get => centerHoverActionIndex;
set
{
if (SetProperty(ref centerHoverActionIndex, value))
{
PreferencesService.CenterHoverAction = availableHoverActions[value];
}
}
}
private int rightHoverActionIndex;
public int RightHoverActionIndex
{
get => rightHoverActionIndex;
set
{
if (SetProperty(ref rightHoverActionIndex, value))
{
PreferencesService.RightHoverAction = availableHoverActions[value];
}
}
}
#endregion
public MessageListPageViewModel(IMailDialogService dialogService,
IPreferencesService preferencesService)
{
PreferencesService = preferencesService;
leftHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.LeftHoverAction);
centerHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.CenterHoverAction);
rightHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.RightHoverAction);
SelectedMarkAsOptionIndex = Array.IndexOf(Enum.GetValues<MailMarkAsOption>(), PreferencesService.MarkAsPreference);
}
}

View File

@@ -2,13 +2,12 @@
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Navigation;
namespace Wino.Mail.ViewModels.Messages
namespace Wino.Mail.ViewModels.Messages;
public class ActiveMailFolderChangedEvent : NavigateMailFolderEventArgs
{
public class ActiveMailFolderChangedEvent : NavigateMailFolderEventArgs
public ActiveMailFolderChangedEvent(IBaseFolderMenuItem baseFolderMenuItem,
TaskCompletionSource<bool> folderInitLoadAwaitTask = null) : base(baseFolderMenuItem, folderInitLoadAwaitTask)
{
public ActiveMailFolderChangedEvent(IBaseFolderMenuItem baseFolderMenuItem,
TaskCompletionSource<bool> folderInitLoadAwaitTask = null) : base(baseFolderMenuItem, folderInitLoadAwaitTask)
{
}
}
}

View File

@@ -1,18 +1,17 @@
using Wino.Mail.ViewModels.Data;
namespace Wino.Mail.ViewModels.Messages
{
/// <summary>
/// When active mail item in the reader is updated.
/// </summary>
public class ActiveMailItemChangedEvent
{
public ActiveMailItemChangedEvent(MailItemViewModel selectedMailItemViewModel)
{
// SelectedMailItemViewModel can be null.
SelectedMailItemViewModel = selectedMailItemViewModel;
}
namespace Wino.Mail.ViewModels.Messages;
public MailItemViewModel SelectedMailItemViewModel { get; set; }
/// <summary>
/// When active mail item in the reader is updated.
/// </summary>
public class ActiveMailItemChangedEvent
{
public ActiveMailItemChangedEvent(MailItemViewModel selectedMailItemViewModel)
{
// SelectedMailItemViewModel can be null.
SelectedMailItemViewModel = selectedMailItemViewModel;
}
public MailItemViewModel SelectedMailItemViewModel { get; set; }
}

View File

@@ -1,19 +1,18 @@
using Wino.Mail.ViewModels.Data;
namespace Wino.Mail.ViewModels.Messages
{
/// <summary>
/// Wino has complex selected item detection mechanism with nested ListViews that
/// supports multi selection with threads. Each list view will raise this for mail list page
/// to react.
/// </summary>
public class MailItemSelectedEvent
{
public MailItemSelectedEvent(MailItemViewModel selectedMailItem)
{
SelectedMailItem = selectedMailItem;
}
namespace Wino.Mail.ViewModels.Messages;
public MailItemViewModel SelectedMailItem { get; set; }
/// <summary>
/// Wino has complex selected item detection mechanism with nested ListViews that
/// supports multi selection with threads. Each list view will raise this for mail list page
/// to react.
/// </summary>
public class MailItemSelectedEvent
{
public MailItemSelectedEvent(MailItemViewModel selectedMailItem)
{
SelectedMailItem = selectedMailItem;
}
public MailItemViewModel SelectedMailItem { get; set; }
}

View File

@@ -1,17 +1,16 @@
using Wino.Mail.ViewModels.Data;
namespace Wino.Mail.ViewModels.Messages
{
/// <summary>
/// Selected item removed event.
/// </summary>
public class MailItemSelectionRemovedEvent
{
public MailItemSelectionRemovedEvent(MailItemViewModel removedMailItem)
{
RemovedMailItem = removedMailItem;
}
namespace Wino.Mail.ViewModels.Messages;
public MailItemViewModel RemovedMailItem { get; set; }
/// <summary>
/// Selected item removed event.
/// </summary>
public class MailItemSelectionRemovedEvent
{
public MailItemSelectionRemovedEvent(MailItemViewModel removedMailItem)
{
RemovedMailItem = removedMailItem;
}
public MailItemViewModel RemovedMailItem { get; set; }
}

View File

@@ -1,11 +1,10 @@
using Wino.Mail.ViewModels.Data;
namespace Wino.Mail.ViewModels.Messages
{
/// <summary>
/// When the rendering page is active, but new item is requested to be rendered.
/// To not trigger navigation again and re-use existing Chromium.
/// </summary>
/// <param name="MailItemViewModel"></param>
public record NewMailItemRenderingRequestedEvent(MailItemViewModel MailItemViewModel);
}
namespace Wino.Mail.ViewModels.Messages;
/// <summary>
/// When the rendering page is active, but new item is requested to be rendered.
/// To not trigger navigation again and re-use existing Chromium.
/// </summary>
/// <param name="MailItemViewModel"></param>
public record NewMailItemRenderingRequestedEvent(MailItemViewModel MailItemViewModel);

View File

@@ -1,9 +1,8 @@
using Wino.Mail.ViewModels.Data;
namespace Wino.Mail.ViewModels.Messages
{
/// <summary>
/// When listing view model manipulated the selected mail container in the UI.
/// </summary>
public record SelectMailItemContainerEvent(MailItemViewModel SelectedMailViewModel, bool ScrollToItem = false);
}
namespace Wino.Mail.ViewModels.Messages;
/// <summary>
/// When listing view model manipulated the selected mail container in the UI.
/// </summary>
public record SelectMailItemContainerEvent(MailItemViewModel SelectedMailViewModel, bool ScrollToItem = false);

View File

@@ -4,70 +4,69 @@ using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Wino.Core.Domain.Interfaces;
namespace Wino.Mail.ViewModels
namespace Wino.Mail.ViewModels;
public partial class ReadComposePanePageViewModel : MailBaseViewModel,
IRecipient<PropertyChangedMessage<string>>,
IRecipient<PropertyChangedMessage<int>>
{
public partial class ReadComposePanePageViewModel : MailBaseViewModel,
IRecipient<PropertyChangedMessage<string>>,
IRecipient<PropertyChangedMessage<int>>
private readonly IFontService _fontService;
public IPreferencesService PreferencesService { get; set; }
public List<string> AvailableFonts => _fontService.GetFonts();
[ObservableProperty]
[NotifyPropertyChangedRecipients]
string currentReaderFont;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
int currentReaderFontSize;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
string currentComposerFont;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
int currentComposerFontSize;
public ReadComposePanePageViewModel(IMailDialogService dialogService,
IFontService fontService,
IPreferencesService preferencesService)
{
private readonly IFontService _fontService;
_fontService = fontService;
PreferencesService = preferencesService;
public IPreferencesService PreferencesService { get; set; }
public List<string> AvailableFonts => _fontService.GetFonts();
CurrentReaderFont = preferencesService.ReaderFont;
CurrentReaderFontSize = preferencesService.ReaderFontSize;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
string currentReaderFont;
CurrentComposerFont = preferencesService.ComposerFont;
CurrentComposerFontSize = preferencesService.ComposerFontSize;
}
[ObservableProperty]
[NotifyPropertyChangedRecipients]
int currentReaderFontSize;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
string currentComposerFont;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
int currentComposerFontSize;
public ReadComposePanePageViewModel(IMailDialogService dialogService,
IFontService fontService,
IPreferencesService preferencesService)
public void Receive(PropertyChangedMessage<string> message)
{
if (message.PropertyName == nameof(CurrentReaderFont) && message.OldValue != message.NewValue)
{
_fontService = fontService;
PreferencesService = preferencesService;
CurrentReaderFont = preferencesService.ReaderFont;
CurrentReaderFontSize = preferencesService.ReaderFontSize;
CurrentComposerFont = preferencesService.ComposerFont;
CurrentComposerFontSize = preferencesService.ComposerFontSize;
PreferencesService.ReaderFont = message.NewValue;
}
public void Receive(PropertyChangedMessage<string> message)
if (message.PropertyName == nameof(CurrentComposerFont) && message.OldValue != message.NewValue)
{
if (message.PropertyName == nameof(CurrentReaderFont) && message.OldValue != message.NewValue)
{
PreferencesService.ReaderFont = message.NewValue;
}
if (message.PropertyName == nameof(CurrentComposerFont) && message.OldValue != message.NewValue)
{
PreferencesService.ComposerFont = message.NewValue;
}
PreferencesService.ComposerFont = message.NewValue;
}
}
public void Receive(PropertyChangedMessage<int> message)
public void Receive(PropertyChangedMessage<int> message)
{
if (message.PropertyName == nameof(CurrentReaderFontSize))
{
if (message.PropertyName == nameof(CurrentReaderFontSize))
{
PreferencesService.ReaderFontSize = CurrentReaderFontSize;
}
else if (message.PropertyName == nameof(CurrentComposerFontSize))
{
PreferencesService.ComposerFontSize = CurrentComposerFontSize;
}
PreferencesService.ReaderFontSize = CurrentReaderFontSize;
}
else if (message.PropertyName == nameof(CurrentComposerFontSize))
{
PreferencesService.ComposerFontSize = CurrentComposerFontSize;
}
}
}

View File

@@ -13,151 +13,150 @@ using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Navigation;
namespace Wino.Mail.ViewModels
namespace Wino.Mail.ViewModels;
public partial class SignatureManagementPageViewModel(IMailDialogService dialogService,
ISignatureService signatureService,
IAccountService accountService) : MailBaseViewModel
{
public partial class SignatureManagementPageViewModel(IMailDialogService dialogService,
ISignatureService signatureService,
IAccountService accountService) : MailBaseViewModel
public ObservableCollection<AccountSignature> Signatures { get; set; } = [];
[ObservableProperty]
private bool isSignatureEnabled;
private int signatureForNewMessagesIndex;
public Guid EmptyGuid { get; } = Guid.Empty;
public int SignatureForNewMessagesIndex
{
public ObservableCollection<AccountSignature> Signatures { get; set; } = [];
[ObservableProperty]
private bool isSignatureEnabled;
private int signatureForNewMessagesIndex;
public Guid EmptyGuid { get; } = Guid.Empty;
public int SignatureForNewMessagesIndex
get => signatureForNewMessagesIndex;
set
{
get => signatureForNewMessagesIndex;
set
if (value == -1)
{
if (value == -1)
{
SetProperty(ref signatureForNewMessagesIndex, 0);
}
else
{
SetProperty(ref signatureForNewMessagesIndex, value);
}
SetProperty(ref signatureForNewMessagesIndex, 0);
}
}
private int signatureForFollowingMessagesIndex;
public int SignatureForFollowingMessagesIndex
{
get => signatureForFollowingMessagesIndex;
set
else
{
if (value == -1)
{
SetProperty(ref signatureForFollowingMessagesIndex, 0);
}
else
{
SetProperty(ref signatureForFollowingMessagesIndex, value);
}
SetProperty(ref signatureForNewMessagesIndex, value);
}
}
private MailAccount Account { get; set; }
private readonly IMailDialogService _dialogService = dialogService;
private readonly ISignatureService _signatureService = signatureService;
private readonly IAccountService _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;
var dbSignatures = await _signatureService.GetSignaturesAsync(Account.Id);
IsSignatureEnabled = Account.Preferences.IsSignatureEnabled;
Signatures.Clear();
Signatures.Add(new AccountSignature { Id = EmptyGuid, Name = Translator.SettingsSignature_NoneSignatureName });
dbSignatures.ForEach(Signatures.Add);
SignatureForNewMessagesIndex = Signatures.IndexOf(Signatures.FirstOrDefault(x => x.Id == Account.Preferences.SignatureIdForNewMessages));
SignatureForFollowingMessagesIndex = Signatures.IndexOf(Signatures.FirstOrDefault(x => x.Id == Account.Preferences.SignatureIdForFollowingMessages));
}
protected override async void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
switch (e.PropertyName)
{
case nameof(IsSignatureEnabled):
Account.Preferences.IsSignatureEnabled = IsSignatureEnabled;
await _accountService.UpdateAccountAsync(Account);
break;
case nameof(SignatureForNewMessagesIndex):
Account.Preferences.SignatureIdForNewMessages = SignatureForNewMessagesIndex > -1
&& Signatures[SignatureForNewMessagesIndex].Id != EmptyGuid
? Signatures[SignatureForNewMessagesIndex].Id : null;
await _accountService.UpdateAccountAsync(Account);
break;
case nameof(SignatureForFollowingMessagesIndex):
Account.Preferences.SignatureIdForFollowingMessages = SignatureForFollowingMessagesIndex > -1
&& Signatures[SignatureForFollowingMessagesIndex].Id != EmptyGuid
? Signatures[SignatureForFollowingMessagesIndex].Id : null;
await _accountService.UpdateAccountAsync(Account);
break;
}
}
[RelayCommand]
private async Task OpenSignatureEditorCreateAsync()
{
var dialogResult = await _dialogService.ShowSignatureEditorDialog();
if (dialogResult == null) return;
dialogResult.MailAccountId = Account.Id;
Signatures.Add(dialogResult);
await _signatureService.CreateSignatureAsync(dialogResult);
}
[RelayCommand]
private async Task OpenSignatureEditorEditAsync(AccountSignature signatureModel)
{
var dialogResult = await _dialogService.ShowSignatureEditorDialog(signatureModel);
if (dialogResult == null) return;
var indexOfCurrentSignature = Signatures.IndexOf(signatureModel);
var signatureNewMessagesIndex = SignatureForNewMessagesIndex;
var signatureFollowingMessagesIndex = SignatureForFollowingMessagesIndex;
Signatures[indexOfCurrentSignature] = dialogResult;
// Reset selection to point updated signature.
// When Item updated/removed index switches to -1. We save index that was used before and update -1 to it.
if (signatureNewMessagesIndex == indexOfCurrentSignature)
SignatureForNewMessagesIndex = indexOfCurrentSignature;
if (signatureFollowingMessagesIndex == indexOfCurrentSignature)
SignatureForFollowingMessagesIndex = indexOfCurrentSignature;
await _signatureService.UpdateSignatureAsync(dialogResult);
}
[RelayCommand]
private async Task DeleteSignatureAsync(AccountSignature signatureModel)
{
var shouldRemove = await _dialogService.ShowConfirmationDialogAsync(string.Format(Translator.SignatureDeleteDialog_Message, signatureModel.Name), Translator.SignatureDeleteDialog_Title, Translator.Buttons_Delete);
if (!shouldRemove) return;
Signatures.Remove(signatureModel);
await _signatureService.DeleteSignatureAsync(signatureModel);
}
}
private int signatureForFollowingMessagesIndex;
public int SignatureForFollowingMessagesIndex
{
get => signatureForFollowingMessagesIndex;
set
{
if (value == -1)
{
SetProperty(ref signatureForFollowingMessagesIndex, 0);
}
else
{
SetProperty(ref signatureForFollowingMessagesIndex, value);
}
}
}
private MailAccount Account { get; set; }
private readonly IMailDialogService _dialogService = dialogService;
private readonly ISignatureService _signatureService = signatureService;
private readonly IAccountService _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;
var dbSignatures = await _signatureService.GetSignaturesAsync(Account.Id);
IsSignatureEnabled = Account.Preferences.IsSignatureEnabled;
Signatures.Clear();
Signatures.Add(new AccountSignature { Id = EmptyGuid, Name = Translator.SettingsSignature_NoneSignatureName });
dbSignatures.ForEach(Signatures.Add);
SignatureForNewMessagesIndex = Signatures.IndexOf(Signatures.FirstOrDefault(x => x.Id == Account.Preferences.SignatureIdForNewMessages));
SignatureForFollowingMessagesIndex = Signatures.IndexOf(Signatures.FirstOrDefault(x => x.Id == Account.Preferences.SignatureIdForFollowingMessages));
}
protected override async void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
switch (e.PropertyName)
{
case nameof(IsSignatureEnabled):
Account.Preferences.IsSignatureEnabled = IsSignatureEnabled;
await _accountService.UpdateAccountAsync(Account);
break;
case nameof(SignatureForNewMessagesIndex):
Account.Preferences.SignatureIdForNewMessages = SignatureForNewMessagesIndex > -1
&& Signatures[SignatureForNewMessagesIndex].Id != EmptyGuid
? Signatures[SignatureForNewMessagesIndex].Id : null;
await _accountService.UpdateAccountAsync(Account);
break;
case nameof(SignatureForFollowingMessagesIndex):
Account.Preferences.SignatureIdForFollowingMessages = SignatureForFollowingMessagesIndex > -1
&& Signatures[SignatureForFollowingMessagesIndex].Id != EmptyGuid
? Signatures[SignatureForFollowingMessagesIndex].Id : null;
await _accountService.UpdateAccountAsync(Account);
break;
}
}
[RelayCommand]
private async Task OpenSignatureEditorCreateAsync()
{
var dialogResult = await _dialogService.ShowSignatureEditorDialog();
if (dialogResult == null) return;
dialogResult.MailAccountId = Account.Id;
Signatures.Add(dialogResult);
await _signatureService.CreateSignatureAsync(dialogResult);
}
[RelayCommand]
private async Task OpenSignatureEditorEditAsync(AccountSignature signatureModel)
{
var dialogResult = await _dialogService.ShowSignatureEditorDialog(signatureModel);
if (dialogResult == null) return;
var indexOfCurrentSignature = Signatures.IndexOf(signatureModel);
var signatureNewMessagesIndex = SignatureForNewMessagesIndex;
var signatureFollowingMessagesIndex = SignatureForFollowingMessagesIndex;
Signatures[indexOfCurrentSignature] = dialogResult;
// Reset selection to point updated signature.
// When Item updated/removed index switches to -1. We save index that was used before and update -1 to it.
if (signatureNewMessagesIndex == indexOfCurrentSignature)
SignatureForNewMessagesIndex = indexOfCurrentSignature;
if (signatureFollowingMessagesIndex == indexOfCurrentSignature)
SignatureForFollowingMessagesIndex = indexOfCurrentSignature;
await _signatureService.UpdateSignatureAsync(dialogResult);
}
[RelayCommand]
private async Task DeleteSignatureAsync(AccountSignature signatureModel)
{
var shouldRemove = await _dialogService.ShowConfirmationDialogAsync(string.Format(Translator.SignatureDeleteDialog_Message, signatureModel.Name), Translator.SignatureDeleteDialog_Title, Translator.Buttons_Delete);
if (!shouldRemove) return;
Signatures.Remove(signatureModel);
await _signatureService.DeleteSignatureAsync(signatureModel);
}
}

View File

@@ -4,35 +4,34 @@ using Wino.Core.Domain;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Navigation;
namespace Wino.Mail.ViewModels
namespace Wino.Mail.ViewModels;
public partial class WelcomePageViewModel : MailBaseViewModel
{
public partial class WelcomePageViewModel : MailBaseViewModel
public const string VersionFile = "190.md";
private readonly IMailDialogService _dialogService;
private readonly IFileService _fileService;
[ObservableProperty]
private string currentVersionNotes;
public WelcomePageViewModel(IMailDialogService dialogService, IFileService fileService)
{
public const string VersionFile = "190.md";
private readonly IMailDialogService _dialogService;
private readonly IFileService _fileService;
_dialogService = dialogService;
_fileService = fileService;
}
[ObservableProperty]
private string currentVersionNotes;
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
{
base.OnNavigatedTo(mode, parameters);
public WelcomePageViewModel(IMailDialogService dialogService, IFileService fileService)
try
{
_dialogService = dialogService;
_fileService = fileService;
CurrentVersionNotes = await _fileService.GetFileContentByApplicationUriAsync($"ms-appx:///Assets/ReleaseNotes/{VersionFile}");
}
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
catch (Exception)
{
base.OnNavigatedTo(mode, parameters);
try
{
CurrentVersionNotes = await _fileService.GetFileContentByApplicationUriAsync($"ms-appx:///Assets/ReleaseNotes/{VersionFile}");
}
catch (Exception)
{
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error, "Can't find the patch notes.", Core.Domain.Enums.InfoBarMessageType.Information);
}
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error, "Can't find the patch notes.", Core.Domain.Enums.InfoBarMessageType.Information);
}
}
}