2024-04-18 01:44:37 +02:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
2024-09-13 02:51:37 +02:00
|
|
|
using System.Threading;
|
2024-04-18 01:44:37 +02:00
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using CommunityToolkit.Mvvm.Input;
|
|
|
|
|
using CommunityToolkit.Mvvm.Messaging;
|
|
|
|
|
using Serilog;
|
|
|
|
|
using Wino.Core.Domain;
|
2024-11-10 23:28:25 +01:00
|
|
|
using Wino.Core.Domain.Entities.Mail;
|
|
|
|
|
using Wino.Core.Domain.Entities.Shared;
|
2024-04-18 01:44:37 +02:00
|
|
|
using Wino.Core.Domain.Enums;
|
|
|
|
|
using Wino.Core.Domain.Exceptions;
|
|
|
|
|
using Wino.Core.Domain.Interfaces;
|
2026-02-15 11:27:30 +01:00
|
|
|
using Wino.Core.Domain.Models.Calendar;
|
2024-04-18 01:44:37 +02:00
|
|
|
using Wino.Core.Domain.Models.Navigation;
|
2026-02-15 19:57:48 +01:00
|
|
|
using Wino.Core.Domain.Models.Synchronization;
|
2025-10-04 23:10:07 +02:00
|
|
|
using Wino.Core.Services;
|
2024-11-10 23:28:25 +01:00
|
|
|
using Wino.Core.ViewModels;
|
|
|
|
|
using Wino.Core.ViewModels.Data;
|
2024-04-18 01:44:37 +02:00
|
|
|
using Wino.Mail.ViewModels.Data;
|
2024-08-05 00:36:26 +02:00
|
|
|
using Wino.Messaging.Client.Navigation;
|
|
|
|
|
using Wino.Messaging.UI;
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
namespace Wino.Mail.ViewModels;
|
|
|
|
|
|
|
|
|
|
public partial class AccountManagementViewModel : AccountManagementPageViewModelBase
|
2024-04-18 01:44:37 +02:00
|
|
|
{
|
2025-02-16 13:23:45 +01:00
|
|
|
private readonly IWinoLogger _winoLogger;
|
2026-02-15 11:27:30 +01:00
|
|
|
private readonly ISpecialImapProviderConfigResolver _specialImapProviderConfigResolver;
|
|
|
|
|
private readonly ICalDavClient _calDavClient;
|
2025-02-16 11:54:23 +01:00
|
|
|
|
|
|
|
|
public IMailDialogService MailDialogService { get; }
|
|
|
|
|
|
|
|
|
|
public AccountManagementViewModel(IMailDialogService dialogService,
|
|
|
|
|
INavigationService navigationService,
|
|
|
|
|
IAccountService accountService,
|
|
|
|
|
IProviderService providerService,
|
|
|
|
|
IStoreManagementService storeManagementService,
|
2026-03-19 01:50:14 +01:00
|
|
|
IWinoAccountProfileService winoAccountProfileService,
|
2025-02-16 13:23:45 +01:00
|
|
|
IWinoLogger winoLogger,
|
2026-02-15 11:27:30 +01:00
|
|
|
ISpecialImapProviderConfigResolver specialImapProviderConfigResolver,
|
|
|
|
|
ICalDavClient calDavClient,
|
2025-02-16 11:54:23 +01:00
|
|
|
IAuthenticationProvider authenticationProvider,
|
2026-03-19 01:50:14 +01:00
|
|
|
IPreferencesService preferencesService) : base(dialogService, navigationService, accountService, providerService, storeManagementService, winoAccountProfileService, authenticationProvider, preferencesService)
|
2025-02-16 11:35:43 +01:00
|
|
|
{
|
2025-02-16 11:54:23 +01:00
|
|
|
MailDialogService = dialogService;
|
2025-02-16 13:23:45 +01:00
|
|
|
_winoLogger = winoLogger;
|
2026-02-15 11:27:30 +01:00
|
|
|
_specialImapProviderConfigResolver = specialImapProviderConfigResolver;
|
|
|
|
|
_calDavClient = calDavClient;
|
2025-02-16 11:54:23 +01:00
|
|
|
}
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
[RelayCommand]
|
|
|
|
|
private async Task CreateMergedAccountAsync()
|
|
|
|
|
{
|
|
|
|
|
var linkName = await DialogService.ShowTextInputDialogAsync(string.Empty, Translator.DialogMessage_CreateLinkedAccountTitle, Translator.DialogMessage_CreateLinkedAccountMessage, Translator.Buttons_Create);
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
if (string.IsNullOrEmpty(linkName)) return;
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
// Create arbitary empty merged inbox with an empty Guid and go to edit page.
|
|
|
|
|
var mergedInbox = new MergedInbox()
|
|
|
|
|
{
|
|
|
|
|
Id = Guid.Empty,
|
|
|
|
|
Name = linkName
|
|
|
|
|
};
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
var mergedAccountProviderDetailViewModel = new MergedAccountProviderDetailViewModel(mergedInbox, new List<AccountProviderDetailViewModel>());
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
Messenger.Send(new BreadcrumbNavigationRequested(mergedAccountProviderDetailViewModel.MergedInbox.Name,
|
|
|
|
|
WinoPage.MergedAccountDetailsPage,
|
|
|
|
|
mergedAccountProviderDetailViewModel));
|
|
|
|
|
}
|
2024-04-18 01:44:37 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
[RelayCommand]
|
|
|
|
|
private async Task AddNewAccountAsync()
|
|
|
|
|
{
|
|
|
|
|
if (IsAccountCreationBlocked)
|
2025-02-16 11:43:30 +01:00
|
|
|
{
|
2025-02-16 11:54:23 +01:00
|
|
|
var isPurchaseClicked = await DialogService.ShowConfirmationDialogAsync(Translator.DialogMessage_AccountLimitMessage, Translator.DialogMessage_AccountLimitTitle, Translator.Buttons_Purchase);
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
if (!isPurchaseClicked) return;
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
await PurchaseUnlimitedAccountAsync();
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2026-03-06 03:42:08 +01:00
|
|
|
Messenger.Send(new BreadcrumbNavigationRequested(Translator.WelcomeWizard_Step2Title, WinoPage.ProviderSelectionPage));
|
2026-02-15 11:27:30 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-05 10:12:03 +01:00
|
|
|
public Task StartAddNewAccountAsync() => AddNewAccountAsync();
|
|
|
|
|
|
2026-02-15 11:27:30 +01:00
|
|
|
private async Task ValidateSpecialImapConnectivityAsync(CustomServerInformation serverInformation)
|
|
|
|
|
{
|
|
|
|
|
var connectivityResult = await SynchronizationManager.Instance
|
|
|
|
|
.TestImapConnectivityAsync(serverInformation, allowSSLHandshake: false)
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
if (connectivityResult.IsCertificateUIRequired)
|
|
|
|
|
{
|
|
|
|
|
var certificateMessage =
|
|
|
|
|
$"{Translator.IMAPSetupDialog_CertificateAllowanceRequired_Row0}\n\n" +
|
|
|
|
|
$"{Translator.IMAPSetupDialog_CertificateIssuer}: {connectivityResult.CertificateIssuer}\n" +
|
|
|
|
|
$"{Translator.IMAPSetupDialog_CertificateValidFrom}: {connectivityResult.CertificateValidFromDateString}\n" +
|
|
|
|
|
$"{Translator.IMAPSetupDialog_CertificateValidTo}: {connectivityResult.CertificateExpirationDateString}\n\n" +
|
|
|
|
|
$"{Translator.IMAPSetupDialog_CertificateAllowanceRequired_Row1}";
|
|
|
|
|
|
|
|
|
|
var allowCertificate = await ExecuteUIThreadTaskAsync(
|
|
|
|
|
() => MailDialogService.ShowConfirmationDialogAsync(certificateMessage, Translator.GeneralTitle_Warning, Translator.Buttons_Allow))
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
if (!allowCertificate)
|
|
|
|
|
throw new InvalidOperationException(Translator.IMAPSetupDialog_CertificateDenied);
|
|
|
|
|
|
|
|
|
|
connectivityResult = await SynchronizationManager.Instance
|
|
|
|
|
.TestImapConnectivityAsync(serverInformation, allowSSLHandshake: true)
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!connectivityResult.IsSuccess)
|
|
|
|
|
throw new InvalidOperationException(connectivityResult.FailedReason ?? Translator.IMAPSetupDialog_ConnectionFailedMessage);
|
|
|
|
|
|
|
|
|
|
if (serverInformation.CalendarSupportMode != ImapCalendarSupportMode.CalDav)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(serverInformation.CalDavServiceUrl))
|
|
|
|
|
throw new InvalidOperationException(Translator.ImapCalDavSettingsPage_CalDavUrlRequired);
|
|
|
|
|
|
|
|
|
|
var settings = new CalDavConnectionSettings
|
|
|
|
|
{
|
|
|
|
|
ServiceUri = new Uri(serverInformation.CalDavServiceUrl, UriKind.Absolute),
|
|
|
|
|
Username = serverInformation.CalDavUsername,
|
|
|
|
|
Password = serverInformation.CalDavPassword
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await _calDavClient.DiscoverCalendarsAsync(settings).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task ExecuteUIThreadTaskAsync(Func<Task> action)
|
|
|
|
|
{
|
|
|
|
|
if (Dispatcher == null)
|
|
|
|
|
{
|
|
|
|
|
await action().ConfigureAwait(false);
|
|
|
|
|
return;
|
2024-04-18 01:44:37 +02:00
|
|
|
}
|
2026-02-15 11:27:30 +01:00
|
|
|
|
|
|
|
|
var completionSource = new TaskCompletionSource<object>();
|
|
|
|
|
|
|
|
|
|
await ExecuteUIThread(() =>
|
|
|
|
|
{
|
|
|
|
|
_ = ExecuteAndCaptureAsync();
|
|
|
|
|
|
|
|
|
|
async Task ExecuteAndCaptureAsync()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await action().ConfigureAwait(false);
|
|
|
|
|
completionSource.TrySetResult(null);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
completionSource.TrySetException(ex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await completionSource.Task.ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<T> ExecuteUIThreadTaskAsync<T>(Func<Task<T>> action)
|
|
|
|
|
{
|
|
|
|
|
if (Dispatcher == null)
|
|
|
|
|
return await action().ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
var completionSource = new TaskCompletionSource<T>();
|
|
|
|
|
|
|
|
|
|
await ExecuteUIThread(() =>
|
|
|
|
|
{
|
|
|
|
|
_ = ExecuteAndCaptureAsync();
|
|
|
|
|
|
|
|
|
|
async Task ExecuteAndCaptureAsync()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var result = await action().ConfigureAwait(false);
|
|
|
|
|
completionSource.TrySetResult(result);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
completionSource.TrySetException(ex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return await completionSource.Task.ConfigureAwait(false);
|
2025-02-16 11:54:23 +01:00
|
|
|
}
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
[RelayCommand]
|
|
|
|
|
private void EditMergedAccounts(MergedAccountProviderDetailViewModel mergedAccountProviderDetailViewModel)
|
|
|
|
|
{
|
|
|
|
|
Messenger.Send(new BreadcrumbNavigationRequested(mergedAccountProviderDetailViewModel.MergedInbox.Name,
|
|
|
|
|
WinoPage.MergedAccountDetailsPage,
|
|
|
|
|
mergedAccountProviderDetailViewModel));
|
|
|
|
|
}
|
2024-05-30 02:34:54 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
[RelayCommand(CanExecute = nameof(CanReorderAccounts))]
|
|
|
|
|
private Task ReorderAccountsAsync() => MailDialogService.ShowAccountReorderDialogAsync(availableAccounts: Accounts);
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
|
|
|
|
|
{
|
|
|
|
|
base.OnNavigatedFrom(mode, parameters);
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
Accounts.CollectionChanged -= AccountCollectionChanged;
|
2024-05-30 02:34:54 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
PropertyChanged -= PagePropertyChanged;
|
|
|
|
|
}
|
2025-02-16 11:35:43 +01:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
private void AccountCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
OnPropertyChanged(nameof(HasAccountsDefined));
|
|
|
|
|
OnPropertyChanged(nameof(UsedAccountsString));
|
|
|
|
|
OnPropertyChanged(nameof(IsAccountCreationAlmostOnLimit));
|
|
|
|
|
|
|
|
|
|
ReorderAccountsCommand.NotifyCanExecuteChanged();
|
|
|
|
|
}
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
private void PagePropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
if (e.PropertyName == nameof(StartupAccount) && StartupAccount != null)
|
2024-04-18 01:44:37 +02:00
|
|
|
{
|
2025-02-16 11:54:23 +01:00
|
|
|
PreferencesService.StartupEntityId = StartupAccount.StartupEntityId;
|
2024-04-18 01:44:37 +02:00
|
|
|
}
|
2025-02-16 11:54:23 +01:00
|
|
|
}
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
|
|
|
|
{
|
|
|
|
|
base.OnNavigatedTo(mode, parameters);
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
Accounts.CollectionChanged -= AccountCollectionChanged;
|
|
|
|
|
Accounts.CollectionChanged += AccountCollectionChanged;
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
await InitializeAccountsAsync();
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
PropertyChanged -= PagePropertyChanged;
|
|
|
|
|
PropertyChanged += PagePropertyChanged;
|
|
|
|
|
}
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
public override async Task InitializeAccountsAsync()
|
|
|
|
|
{
|
|
|
|
|
StartupAccount = null;
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
Accounts.Clear();
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
var accounts = await AccountService.GetAccountsAsync().ConfigureAwait(false);
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
// Group accounts and display merged ones at the top.
|
|
|
|
|
var groupedAccounts = accounts.GroupBy(a => a.MergedInboxId);
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
await ExecuteUIThread(() =>
|
|
|
|
|
{
|
|
|
|
|
foreach (var accountGroup in groupedAccounts)
|
2024-04-18 01:44:37 +02:00
|
|
|
{
|
2025-02-16 11:54:23 +01:00
|
|
|
var mergedInboxId = accountGroup.Key;
|
2025-02-16 11:43:30 +01:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
if (mergedInboxId == null)
|
|
|
|
|
{
|
|
|
|
|
foreach (var account in accountGroup)
|
2024-04-18 01:44:37 +02:00
|
|
|
{
|
2025-02-16 11:54:23 +01:00
|
|
|
var accountDetails = GetAccountProviderDetails(account);
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
Accounts.Add(accountDetails);
|
2024-04-18 01:44:37 +02:00
|
|
|
}
|
2025-02-16 11:54:23 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var mergedInbox = accountGroup.First(a => a.MergedInboxId == mergedInboxId).MergedInbox;
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
var holdingAccountProviderDetails = accountGroup.Select(a => GetAccountProviderDetails(a)).ToList();
|
|
|
|
|
var mergedAccountViewModel = new MergedAccountProviderDetailViewModel(mergedInbox, holdingAccountProviderDetails);
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
Accounts.Add(mergedAccountViewModel);
|
2024-04-18 01:44:37 +02:00
|
|
|
}
|
2025-02-16 11:54:23 +01:00
|
|
|
}
|
2024-04-18 01:44:37 +02:00
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
// Handle startup entity.
|
|
|
|
|
if (PreferencesService.StartupEntityId != null)
|
|
|
|
|
{
|
|
|
|
|
StartupAccount = Accounts.FirstOrDefault(a => a.StartupEntityId == PreferencesService.StartupEntityId);
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-04-18 01:44:37 +02:00
|
|
|
|
|
|
|
|
|
2025-02-16 11:54:23 +01:00
|
|
|
await ManageStorePurchasesAsync().ConfigureAwait(false);
|
2024-04-18 01:44:37 +02:00
|
|
|
}
|
|
|
|
|
}
|