Imap flow.
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
namespace Wino.Core.Domain.Models.Navigation;
|
||||||
|
|
||||||
|
public enum ProviderSelectionHostMode
|
||||||
|
{
|
||||||
|
Wizard,
|
||||||
|
SettingsAddAccount
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ProviderSelectionNavigationContext
|
||||||
|
{
|
||||||
|
public ProviderSelectionHostMode HostMode { get; init; } = ProviderSelectionHostMode.Wizard;
|
||||||
|
|
||||||
|
public static ProviderSelectionNavigationContext CreateForWizard()
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
HostMode = ProviderSelectionHostMode.Wizard
|
||||||
|
};
|
||||||
|
|
||||||
|
public static ProviderSelectionNavigationContext CreateForSettingsAddAccount()
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
HostMode = ProviderSelectionHostMode.SettingsAddAccount
|
||||||
|
};
|
||||||
|
|
||||||
|
public bool IsWizardHost => HostMode == ProviderSelectionHostMode.Wizard;
|
||||||
|
}
|
||||||
@@ -141,7 +141,9 @@ public static class SettingsNavigationInfoProvider
|
|||||||
public static SettingsNavigationItemInfo GetInfo(WinoPage pageType, string manageAccountsDescription = "")
|
public static SettingsNavigationItemInfo GetInfo(WinoPage pageType, string manageAccountsDescription = "")
|
||||||
{
|
{
|
||||||
var rootPage = GetRootPage(pageType);
|
var rootPage = GetRootPage(pageType);
|
||||||
return GetNavigationItems(manageAccountsDescription).First(item => item.PageType == rootPage);
|
return GetNavigationItems(manageAccountsDescription)
|
||||||
|
.FirstOrDefault(item => item.PageType == rootPage)
|
||||||
|
?? GetNavigationItems(manageAccountsDescription).First(item => item.PageType == WinoPage.SettingOptionsPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetPageTitle(WinoPage pageType)
|
public static string GetPageTitle(WinoPage pageType)
|
||||||
@@ -180,6 +182,9 @@ public static class SettingsNavigationInfoProvider
|
|||||||
WinoPage.MailCategoryManagementPage => WinoPage.ManageAccountsPage,
|
WinoPage.MailCategoryManagementPage => WinoPage.ManageAccountsPage,
|
||||||
WinoPage.SignatureManagementPage => WinoPage.ManageAccountsPage,
|
WinoPage.SignatureManagementPage => WinoPage.ManageAccountsPage,
|
||||||
WinoPage.ImapCalDavSettingsPage => WinoPage.ManageAccountsPage,
|
WinoPage.ImapCalDavSettingsPage => WinoPage.ManageAccountsPage,
|
||||||
|
WinoPage.ProviderSelectionPage => WinoPage.ManageAccountsPage,
|
||||||
|
WinoPage.SpecialImapCredentialsPage => WinoPage.ManageAccountsPage,
|
||||||
|
WinoPage.AccountSetupProgressPage => WinoPage.ManageAccountsPage,
|
||||||
WinoPage.CreateEmailTemplatePage => WinoPage.EmailTemplatesPage,
|
WinoPage.CreateEmailTemplatePage => WinoPage.EmailTemplatesPage,
|
||||||
WinoPage.CalendarSettingsPage => WinoPage.CalendarPreferenceSettingsPage,
|
WinoPage.CalendarSettingsPage => WinoPage.CalendarPreferenceSettingsPage,
|
||||||
WinoPage.CalendarAccountSettingsPage => WinoPage.CalendarPreferenceSettingsPage,
|
WinoPage.CalendarAccountSettingsPage => WinoPage.CalendarPreferenceSettingsPage,
|
||||||
|
|||||||
@@ -438,7 +438,7 @@
|
|||||||
"IMAPAdvancedSetupDialog_ValidationAuthMethodRequired": "Authentication method is required",
|
"IMAPAdvancedSetupDialog_ValidationAuthMethodRequired": "Authentication method is required",
|
||||||
"IMAPAdvancedSetupDialog_ValidationConnectionSecurityRequired": "Connection security type is required",
|
"IMAPAdvancedSetupDialog_ValidationConnectionSecurityRequired": "Connection security type is required",
|
||||||
"IMAPAdvancedSetupDialog_ValidationDisplayNameRequired": "Display name is required",
|
"IMAPAdvancedSetupDialog_ValidationDisplayNameRequired": "Display name is required",
|
||||||
"IMAPAdvancedSetupDialog_ValidationEmailInvalid": "Please enter a valid email address",
|
"IMAPAdvancedSetupDialog_ValidationEmailInvalid": "Please enter a valid mailbox address, such as user@example.com or user@localhost",
|
||||||
"IMAPAdvancedSetupDialog_ValidationEmailRequired": "Email address is required",
|
"IMAPAdvancedSetupDialog_ValidationEmailRequired": "Email address is required",
|
||||||
"IMAPAdvancedSetupDialog_ValidationErrorTitle": "Please check the following:",
|
"IMAPAdvancedSetupDialog_ValidationErrorTitle": "Please check the following:",
|
||||||
"IMAPAdvancedSetupDialog_ValidationIncomingPortInvalid": "Incoming port must be between 1-65535",
|
"IMAPAdvancedSetupDialog_ValidationIncomingPortInvalid": "Incoming port must be between 1-65535",
|
||||||
@@ -485,7 +485,7 @@
|
|||||||
"IMAPSetupDialog_IMAPSettings": "IMAP Server Settings",
|
"IMAPSetupDialog_IMAPSettings": "IMAP Server Settings",
|
||||||
"IMAPSetupDialog_SMTPSettings": "SMTP Server Settings",
|
"IMAPSetupDialog_SMTPSettings": "SMTP Server Settings",
|
||||||
"IMAPSetupDialog_MailAddress": "Email address",
|
"IMAPSetupDialog_MailAddress": "Email address",
|
||||||
"IMAPSetupDialog_MailAddressPlaceholder": "someone@example.com",
|
"IMAPSetupDialog_MailAddressPlaceholder": "someone@example.com or user@localhost",
|
||||||
"IMAPSetupDialog_OutgoingMailServer": "Outgoing (SMTP) mail server",
|
"IMAPSetupDialog_OutgoingMailServer": "Outgoing (SMTP) mail server",
|
||||||
"IMAPSetupDialog_OutgoingMailServerPassword": "Outgoing server password",
|
"IMAPSetupDialog_OutgoingMailServerPassword": "Outgoing server password",
|
||||||
"IMAPSetupDialog_OutgoingMailServerPort": "Port",
|
"IMAPSetupDialog_OutgoingMailServerPort": "Port",
|
||||||
@@ -502,7 +502,7 @@
|
|||||||
"ImapCalDavSettingsPage_TitleEdit": "Edit IMAP and Calendar Settings",
|
"ImapCalDavSettingsPage_TitleEdit": "Edit IMAP and Calendar Settings",
|
||||||
"ImapCalDavSettingsPage_Subtitle": "Configure IMAP/SMTP and optional calendar synchronization for this account.",
|
"ImapCalDavSettingsPage_Subtitle": "Configure IMAP/SMTP and optional calendar synchronization for this account.",
|
||||||
"ImapCalDavSettingsPage_BasicSectionTitle": "Basic setup",
|
"ImapCalDavSettingsPage_BasicSectionTitle": "Basic setup",
|
||||||
"ImapCalDavSettingsPage_BasicSectionDescription": "Enter your identity and credentials. Wino can try to detect server settings automatically.",
|
"ImapCalDavSettingsPage_BasicSectionDescription": "Enter your identity and credentials. Wino supports manual addresses such as user@localhost and can try to detect server settings automatically.",
|
||||||
"ImapCalDavSettingsPage_BasicTab": "Basic",
|
"ImapCalDavSettingsPage_BasicTab": "Basic",
|
||||||
"ImapCalDavSettingsPage_EnableCalendarSupport": "Enable calendar support",
|
"ImapCalDavSettingsPage_EnableCalendarSupport": "Enable calendar support",
|
||||||
"ImapCalDavSettingsPage_AutoDiscoverButton": "Autodiscover mail settings",
|
"ImapCalDavSettingsPage_AutoDiscoverButton": "Autodiscover mail settings",
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net.Mail;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Validation;
|
||||||
|
|
||||||
|
public static class MailAccountAddressValidator
|
||||||
|
{
|
||||||
|
public static bool IsValid(string address)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(address))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var trimmedAddress = address.Trim();
|
||||||
|
|
||||||
|
if (trimmedAddress.Contains('\r') || trimmedAddress.Contains('\n'))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var parsedAddress = new MailAddress(trimmedAddress);
|
||||||
|
return parsedAddress.Address.Equals(trimmedAddress, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetDomain(string address, out string domain)
|
||||||
|
{
|
||||||
|
domain = string.Empty;
|
||||||
|
|
||||||
|
if (!IsValid(address))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var trimmedAddress = address.Trim();
|
||||||
|
var separatorIndex = trimmedAddress.LastIndexOf('@');
|
||||||
|
|
||||||
|
if (separatorIndex <= 0 || separatorIndex >= trimmedAddress.Length - 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
domain = trimmedAddress[(separatorIndex + 1)..];
|
||||||
|
return !string.IsNullOrWhiteSpace(domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsImplicitlyResolvableHost(string host)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(host))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var normalizedHost = host.Trim().TrimEnd('.');
|
||||||
|
if (string.IsNullOrWhiteSpace(normalizedHost))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (normalizedHost.Equals("localhost", StringComparison.OrdinalIgnoreCase))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var hostType = Uri.CheckHostName(normalizedHost);
|
||||||
|
if (hostType is UriHostNameType.IPv4 or UriHostNameType.IPv6)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return normalizedHost.IndexOf('.') < 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -150,6 +150,41 @@ public class AutoDiscoveryServiceTests
|
|||||||
uri.Should().Be(new Uri("https://dav.example.net/caldav/"));
|
uri.Should().Be(new Uri("https://dav.example.net/caldav/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAutoDiscoverySettings_ReturnsGuessedLocalhostSettings_ForManualLocalAccounts()
|
||||||
|
{
|
||||||
|
var handler = new StubHttpMessageHandler(request =>
|
||||||
|
{
|
||||||
|
var uri = request.RequestUri!.ToString();
|
||||||
|
|
||||||
|
if (uri.StartsWith("https://dns.google/resolve", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return CreateJsonResponse("{\"Status\":0}", request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateStatusResponse(HttpStatusCode.NotFound, request);
|
||||||
|
});
|
||||||
|
|
||||||
|
using var client = new HttpClient(handler);
|
||||||
|
var sut = new AutoDiscoveryService(client);
|
||||||
|
|
||||||
|
var settings = await sut.GetAutoDiscoverySettings(new AutoDiscoveryMinimalSettings
|
||||||
|
{
|
||||||
|
Email = "user@localhost",
|
||||||
|
DisplayName = "User",
|
||||||
|
Password = "secret"
|
||||||
|
});
|
||||||
|
|
||||||
|
settings.Should().NotBeNull();
|
||||||
|
settings!.Domain.Should().Be("localhost");
|
||||||
|
settings.GetImapSettings()!.Address.Should().Be("localhost");
|
||||||
|
settings.GetImapSettings()!.Port.Should().Be(993);
|
||||||
|
settings.GetImapSettings()!.Username.Should().Be("user@localhost");
|
||||||
|
settings.GetSmptpSettings()!.Address.Should().Be("localhost");
|
||||||
|
settings.GetSmptpSettings()!.Port.Should().Be(587);
|
||||||
|
settings.GetSmptpSettings()!.Username.Should().Be("user@localhost");
|
||||||
|
}
|
||||||
|
|
||||||
private static HttpResponseMessage CreateXmlResponse(string xml, HttpRequestMessage request)
|
private static HttpResponseMessage CreateXmlResponse(string xml, HttpRequestMessage request)
|
||||||
=> new(HttpStatusCode.OK)
|
=> new(HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -160,7 +160,10 @@ public partial class SettingOptionsPageViewModel : CoreBaseViewModel
|
|||||||
public void NavigateToAddAccount()
|
public void NavigateToAddAccount()
|
||||||
{
|
{
|
||||||
Messenger.Send(new BreadcrumbNavigationRequested(Translator.SettingsManageAccountSettings_Title, WinoPage.ManageAccountsPage));
|
Messenger.Send(new BreadcrumbNavigationRequested(Translator.SettingsManageAccountSettings_Title, WinoPage.ManageAccountsPage));
|
||||||
Messenger.Send(new BreadcrumbNavigationRequested(Translator.WelcomeWizard_Step2Title, WinoPage.ProviderSelectionPage));
|
Messenger.Send(new BreadcrumbNavigationRequested(
|
||||||
|
Translator.WelcomeWizard_Step2Title,
|
||||||
|
WinoPage.ProviderSelectionPage,
|
||||||
|
ProviderSelectionNavigationContext.CreateForSettingsAddAccount()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void NavigateToManageAccounts()
|
public void NavigateToManageAccounts()
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ public class ImapClientPool : IDisposable
|
|||||||
private readonly int _targetMinimumConnections;
|
private readonly int _targetMinimumConnections;
|
||||||
|
|
||||||
private DateTime _lastKeepAliveSentUtc = DateTime.MinValue;
|
private DateTime _lastKeepAliveSentUtc = DateTime.MinValue;
|
||||||
|
private Exception _lastConnectionException;
|
||||||
private WinoImapClient _dedicatedIdleClient;
|
private WinoImapClient _dedicatedIdleClient;
|
||||||
private bool _disposedValue;
|
private bool _disposedValue;
|
||||||
private bool _initialized;
|
private bool _initialized;
|
||||||
@@ -114,7 +115,7 @@ public class ImapClientPool : IDisposable
|
|||||||
var initialClient = await CreateAndConnectClientAsync(cancellationToken).ConfigureAwait(false);
|
var initialClient = await CreateAndConnectClientAsync(cancellationToken).ConfigureAwait(false);
|
||||||
if (initialClient == null)
|
if (initialClient == null)
|
||||||
{
|
{
|
||||||
throw CreatePoolException("Failed to create initial IMAP connection for the pool.");
|
throw CreatePoolException("Failed to create initial IMAP connection for the pool.", _lastConnectionException);
|
||||||
}
|
}
|
||||||
|
|
||||||
_clientStates[initialClient] = ImapClientState.Available;
|
_clientStates[initialClient] = ImapClientState.Available;
|
||||||
@@ -192,6 +193,7 @@ public class ImapClientPool : IDisposable
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_lastConnectionException = ex;
|
||||||
_logger.Warning(ex, "Pooled IMAP client was not ready. Marking as failed.");
|
_logger.Warning(ex, "Pooled IMAP client was not ready. Marking as failed.");
|
||||||
MarkClientAsFailed(pooledClient);
|
MarkClientAsFailed(pooledClient);
|
||||||
}
|
}
|
||||||
@@ -215,12 +217,12 @@ public class ImapClientPool : IDisposable
|
|||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
|
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
throw CreatePoolException($"Timed out while acquiring an IMAP client after {timeout.TotalSeconds:F1} seconds. Failures: {createFailures}.");
|
throw CreatePoolException($"Timed out while acquiring an IMAP client after {timeout.TotalSeconds:F1} seconds. Failures: {createFailures}.", _lastConnectionException);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw cancellationToken.IsCancellationRequested
|
throw cancellationToken.IsCancellationRequested
|
||||||
? new OperationCanceledException(cancellationToken)
|
? new OperationCanceledException(cancellationToken)
|
||||||
: CreatePoolException($"Failed to acquire IMAP client within {timeout.TotalSeconds:F1} seconds. Failures: {createFailures}.");
|
: CreatePoolException($"Failed to acquire IMAP client within {timeout.TotalSeconds:F1} seconds. Failures: {createFailures}.", _lastConnectionException);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -516,14 +518,17 @@ public class ImapClientPool : IDisposable
|
|||||||
private async Task<WinoImapClient> CreateAndConnectClientAsync(CancellationToken cancellationToken)
|
private async Task<WinoImapClient> CreateAndConnectClientAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var client = CreateNewClient();
|
var client = CreateNewClient();
|
||||||
|
_lastConnectionException = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await EnsureClientReadyAsync(client, cancellationToken).ConfigureAwait(false);
|
await EnsureClientReadyAsync(client, cancellationToken).ConfigureAwait(false);
|
||||||
|
_lastConnectionException = null;
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_lastConnectionException = ex;
|
||||||
_logger.Warning(ex, "Failed to create and connect IMAP client.");
|
_logger.Warning(ex, "Failed to create and connect IMAP client.");
|
||||||
DisposeClient(client);
|
DisposeClient(client);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using Serilog;
|
|||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models;
|
using Wino.Core.Domain.Models;
|
||||||
using Wino.Core.Domain.Models.AutoDiscovery;
|
using Wino.Core.Domain.Models.AutoDiscovery;
|
||||||
|
using Wino.Core.Domain.Validation;
|
||||||
|
|
||||||
namespace Wino.Core.Services;
|
namespace Wino.Core.Services;
|
||||||
|
|
||||||
@@ -450,6 +451,9 @@ public class AutoDiscoveryService : IAutoDiscoveryService
|
|||||||
|
|
||||||
private async Task<bool> HasAnyDnsAddressRecordAsync(string host, CancellationToken cancellationToken)
|
private async Task<bool> HasAnyDnsAddressRecordAsync(string host, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
if (MailAccountAddressValidator.IsImplicitlyResolvableHost(host))
|
||||||
|
return true;
|
||||||
|
|
||||||
var aRecords = await QueryDnsAsync(host, "A", cancellationToken).ConfigureAwait(false);
|
var aRecords = await QueryDnsAsync(host, "A", cancellationToken).ConfigureAwait(false);
|
||||||
if (aRecords.Count > 0)
|
if (aRecords.Count > 0)
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ public class SynchronizationManager : ISynchronizationManager, IRecipient<Accoun
|
|||||||
catch (ImapClientPoolException clientPoolException)
|
catch (ImapClientPoolException clientPoolException)
|
||||||
{
|
{
|
||||||
_logger.Error(clientPoolException, "IMAP connectivity test failed");
|
_logger.Error(clientPoolException, "IMAP connectivity test failed");
|
||||||
return ImapConnectivityTestResults.Failure(clientPoolException);
|
return ImapConnectivityTestResults.Failure(clientPoolException.InnerException ?? clientPoolException);
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -103,7 +103,10 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Messenger.Send(new BreadcrumbNavigationRequested(Translator.WelcomeWizard_Step2Title, WinoPage.ProviderSelectionPage));
|
Messenger.Send(new BreadcrumbNavigationRequested(
|
||||||
|
Translator.WelcomeWizard_Step2Title,
|
||||||
|
WinoPage.ProviderSelectionPage,
|
||||||
|
ProviderSelectionNavigationContext.CreateForSettingsAddAccount()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StartAddNewAccountAsync() => AddNewAccountAsync();
|
public Task StartAddNewAccountAsync() => AddNewAccountAsync();
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ public enum ImapCalDavSettingsPageMode
|
|||||||
{
|
{
|
||||||
Create,
|
Create,
|
||||||
Edit,
|
Edit,
|
||||||
Wizard
|
Wizard,
|
||||||
|
AddAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ImapCalDavSettingsNavigationContext
|
public sealed class ImapCalDavSettingsNavigationContext
|
||||||
@@ -45,6 +46,14 @@ public sealed class ImapCalDavSettingsNavigationContext
|
|||||||
AccountCreationDialogResult = accountCreationDialogResult
|
AccountCreationDialogResult = accountCreationDialogResult
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static ImapCalDavSettingsNavigationContext CreateForAddAccountMode(
|
||||||
|
AccountCreationDialogResult accountCreationDialogResult)
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
Mode = ImapCalDavSettingsPageMode.AddAccount,
|
||||||
|
AccountCreationDialogResult = accountCreationDialogResult
|
||||||
|
};
|
||||||
|
|
||||||
public bool IsWizardMode => Mode == ImapCalDavSettingsPageMode.Wizard;
|
public bool IsWizardMode => Mode == ImapCalDavSettingsPageMode.Wizard;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using Wino.Core.Domain.Models.AutoDiscovery;
|
|||||||
using Wino.Core.Domain.Models.Calendar;
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
using Wino.Core.Domain.Models.Navigation;
|
using Wino.Core.Domain.Models.Navigation;
|
||||||
using Wino.Core.Domain.Models.Synchronization;
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
|
using Wino.Core.Domain.Validation;
|
||||||
using Wino.Core.Services;
|
using Wino.Core.Services;
|
||||||
using Wino.Mail.ViewModels.Data;
|
using Wino.Mail.ViewModels.Data;
|
||||||
using Wino.Messaging.Client.Navigation;
|
using Wino.Messaging.Client.Navigation;
|
||||||
@@ -135,7 +136,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
|||||||
[NotifyPropertyChangedFor(nameof(IsAdvancedSetupSelected))]
|
[NotifyPropertyChangedFor(nameof(IsAdvancedSetupSelected))]
|
||||||
private int selectedSetupTabIndex;
|
private int selectedSetupTabIndex;
|
||||||
|
|
||||||
public bool IsCreateMode => _pageMode == ImapCalDavSettingsPageMode.Create;
|
public bool IsCreateMode => _pageMode is ImapCalDavSettingsPageMode.Create or ImapCalDavSettingsPageMode.AddAccount;
|
||||||
public bool IsEditMode => !IsCreateMode;
|
public bool IsEditMode => !IsCreateMode;
|
||||||
public bool HasProviderHint => !string.IsNullOrWhiteSpace(ProviderHint);
|
public bool HasProviderHint => !string.IsNullOrWhiteSpace(ProviderHint);
|
||||||
public bool IsBasicSetupSelected => SelectedSetupTabIndex == 0;
|
public bool IsBasicSetupSelected => SelectedSetupTabIndex == 0;
|
||||||
@@ -289,7 +290,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
|||||||
_localOnlyInfoShown = false;
|
_localOnlyInfoShown = false;
|
||||||
SelectedSetupTabIndex = 0;
|
SelectedSetupTabIndex = 0;
|
||||||
|
|
||||||
if (_pageMode == ImapCalDavSettingsPageMode.Create || _pageMode == ImapCalDavSettingsPageMode.Wizard)
|
if (_pageMode is ImapCalDavSettingsPageMode.Create or ImapCalDavSettingsPageMode.Wizard or ImapCalDavSettingsPageMode.AddAccount)
|
||||||
{
|
{
|
||||||
PageTitle = Translator.ImapCalDavSettingsPage_TitleCreate;
|
PageTitle = Translator.ImapCalDavSettingsPage_TitleCreate;
|
||||||
ApplyCreateContextDefaults(context.AccountCreationDialogResult);
|
ApplyCreateContextDefaults(context.AccountCreationDialogResult);
|
||||||
@@ -412,7 +413,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
|||||||
? _editingAccountId
|
? _editingAccountId
|
||||||
: (Guid?)null;
|
: (Guid?)null;
|
||||||
|
|
||||||
if (!await ValidateAccountUniquenessAsync(excludedAccountId).ConfigureAwait(false))
|
if (!await ValidateAccountUniquenessAsync(excludedAccountId))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await ValidateImapConnectivityAsync(serverInformation);
|
await ValidateImapConnectivityAsync(serverInformation);
|
||||||
@@ -434,6 +435,12 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_pageMode == ImapCalDavSettingsPageMode.AddAccount)
|
||||||
|
{
|
||||||
|
CompleteAddAccountFlow(serverInformation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (_pageMode == ImapCalDavSettingsPageMode.Create)
|
if (_pageMode == ImapCalDavSettingsPageMode.Create)
|
||||||
{
|
{
|
||||||
CompleteCreateFlow(serverInformation);
|
CompleteCreateFlow(serverInformation);
|
||||||
@@ -464,6 +471,12 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void CompleteWizardFlow(CustomServerInformation serverInformation)
|
private void CompleteWizardFlow(CustomServerInformation serverInformation)
|
||||||
|
=> ContinueAccountCreationFlow(serverInformation);
|
||||||
|
|
||||||
|
private void CompleteAddAccountFlow(CustomServerInformation serverInformation)
|
||||||
|
=> ContinueAccountCreationFlow(serverInformation);
|
||||||
|
|
||||||
|
private void ContinueAccountCreationFlow(CustomServerInformation serverInformation)
|
||||||
{
|
{
|
||||||
serverInformation.Id = Guid.NewGuid();
|
serverInformation.Id = Guid.NewGuid();
|
||||||
serverInformation.AccountId = Guid.Empty;
|
serverInformation.AccountId = Guid.Empty;
|
||||||
@@ -512,6 +525,31 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
partial void OnEmailAddressChanged(string oldValue, string newValue)
|
||||||
|
{
|
||||||
|
var previousAddress = oldValue?.Trim() ?? string.Empty;
|
||||||
|
var currentAddress = newValue?.Trim() ?? string.Empty;
|
||||||
|
|
||||||
|
ApplyCredentialDefaultsForAddress(previousAddress, currentAddress);
|
||||||
|
ApplyManualServerDefaultsForAddress(previousAddress, currentAddress);
|
||||||
|
|
||||||
|
IsImapValidationSucceeded = false;
|
||||||
|
IsCalDavValidationSucceeded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnPasswordChanged(string oldValue, string newValue)
|
||||||
|
{
|
||||||
|
var previousPassword = oldValue ?? string.Empty;
|
||||||
|
var currentPassword = newValue ?? string.Empty;
|
||||||
|
|
||||||
|
IncomingServerPassword = ReplaceIfEmptyOrMatchingPrevious(IncomingServerPassword, previousPassword, currentPassword);
|
||||||
|
OutgoingServerPassword = ReplaceIfEmptyOrMatchingPrevious(OutgoingServerPassword, previousPassword, currentPassword);
|
||||||
|
CalDavPassword = ReplaceIfEmptyOrMatchingPrevious(CalDavPassword, previousPassword, currentPassword);
|
||||||
|
|
||||||
|
IsImapValidationSucceeded = false;
|
||||||
|
IsCalDavValidationSucceeded = false;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task InitializeEditModeAsync(Guid accountId)
|
private async Task InitializeEditModeAsync(Guid accountId)
|
||||||
{
|
{
|
||||||
var account = await _accountService.GetAccountAsync(accountId);
|
var account = await _accountService.GetAccountAsync(accountId);
|
||||||
@@ -618,6 +656,50 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
|||||||
SelectedOutgoingServerAuthenticationMethodIndex = 0;
|
SelectedOutgoingServerAuthenticationMethodIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ApplyCredentialDefaultsForAddress(string previousAddress, string currentAddress)
|
||||||
|
{
|
||||||
|
IncomingServerUsername = ReplaceIfEmptyOrMatchingPrevious(IncomingServerUsername, previousAddress, currentAddress);
|
||||||
|
OutgoingServerUsername = ReplaceIfEmptyOrMatchingPrevious(OutgoingServerUsername, previousAddress, currentAddress);
|
||||||
|
CalDavUsername = ReplaceIfEmptyOrMatchingPrevious(CalDavUsername, previousAddress, currentAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyManualServerDefaultsForAddress(string previousAddress, string currentAddress)
|
||||||
|
{
|
||||||
|
if (!MailAccountAddressValidator.TryGetDomain(currentAddress, out var currentDomain) ||
|
||||||
|
!MailAccountAddressValidator.IsImplicitlyResolvableHost(currentDomain))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MailAccountAddressValidator.TryGetDomain(previousAddress, out var previousDomain);
|
||||||
|
|
||||||
|
IncomingServer = ReplaceIfEmptyOrMatchingPrevious(IncomingServer, previousDomain, currentDomain);
|
||||||
|
OutgoingServer = ReplaceIfEmptyOrMatchingPrevious(OutgoingServer, previousDomain, currentDomain);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(IncomingServerPort))
|
||||||
|
IncomingServerPort = "993";
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(OutgoingServerPort))
|
||||||
|
OutgoingServerPort = "587";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ReplaceIfEmptyOrMatchingPrevious(string currentValue, string previousValue, string replacementValue)
|
||||||
|
{
|
||||||
|
var normalizedCurrentValue = currentValue?.Trim() ?? string.Empty;
|
||||||
|
var normalizedPreviousValue = previousValue?.Trim() ?? string.Empty;
|
||||||
|
var normalizedReplacementValue = replacementValue?.Trim() ?? string.Empty;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(normalizedReplacementValue))
|
||||||
|
return currentValue ?? string.Empty;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(normalizedCurrentValue))
|
||||||
|
return normalizedReplacementValue;
|
||||||
|
|
||||||
|
return string.Equals(normalizedCurrentValue, normalizedPreviousValue, StringComparison.OrdinalIgnoreCase)
|
||||||
|
? normalizedReplacementValue
|
||||||
|
: currentValue ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
private void ApplyServerInformation(CustomServerInformation serverInformation)
|
private void ApplyServerInformation(CustomServerInformation serverInformation)
|
||||||
{
|
{
|
||||||
if (serverInformation == null)
|
if (serverInformation == null)
|
||||||
@@ -809,7 +891,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
|||||||
|
|
||||||
private async Task SaveEditFlowAsync(CustomServerInformation serverInformation)
|
private async Task SaveEditFlowAsync(CustomServerInformation serverInformation)
|
||||||
{
|
{
|
||||||
var account = await _accountService.GetAccountAsync(_editingAccountId).ConfigureAwait(false);
|
var account = await _accountService.GetAccountAsync(_editingAccountId);
|
||||||
if (account == null)
|
if (account == null)
|
||||||
throw new InvalidOperationException(Translator.Exception_NullAssignedAccount);
|
throw new InvalidOperationException(Translator.Exception_NullAssignedAccount);
|
||||||
|
|
||||||
@@ -823,8 +905,8 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
|||||||
account.ServerInformation = serverInformation;
|
account.ServerInformation = serverInformation;
|
||||||
account.AttentionReason = AccountAttentionReason.None;
|
account.AttentionReason = AccountAttentionReason.None;
|
||||||
|
|
||||||
await _accountService.UpdateAccountCustomServerInformationAsync(serverInformation).ConfigureAwait(false);
|
await _accountService.UpdateAccountCustomServerInformationAsync(serverInformation);
|
||||||
await _accountService.UpdateAccountAsync(account).ConfigureAwait(false);
|
await _accountService.UpdateAccountAsync(account);
|
||||||
|
|
||||||
Messenger.Send(new NewMailSynchronizationRequested(new MailSynchronizationOptions
|
Messenger.Send(new NewMailSynchronizationRequested(new MailSynchronizationOptions
|
||||||
{
|
{
|
||||||
@@ -916,7 +998,7 @@ public partial class ImapCalDavSettingsPageViewModel : MailBaseViewModel
|
|||||||
if (string.IsNullOrWhiteSpace(EmailAddress))
|
if (string.IsNullOrWhiteSpace(EmailAddress))
|
||||||
throw new InvalidOperationException(Translator.IMAPAdvancedSetupDialog_ValidationEmailRequired);
|
throw new InvalidOperationException(Translator.IMAPAdvancedSetupDialog_ValidationEmailRequired);
|
||||||
|
|
||||||
if (!EmailValidation.EmailValidator.Validate(EmailAddress.Trim()))
|
if (!MailAccountAddressValidator.IsValid(EmailAddress))
|
||||||
throw new InvalidOperationException(Translator.IMAPAdvancedSetupDialog_ValidationEmailInvalid);
|
throw new InvalidOperationException(Translator.IMAPAdvancedSetupDialog_ValidationEmailInvalid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public partial class ProviderSelectionPageViewModel : MailBaseViewModel
|
|||||||
private readonly IDialogServiceBase _dialogService;
|
private readonly IDialogServiceBase _dialogService;
|
||||||
private readonly IProviderService _providerService;
|
private readonly IProviderService _providerService;
|
||||||
private readonly INewThemeService _themeService;
|
private readonly INewThemeService _themeService;
|
||||||
|
private ProviderSelectionHostMode _hostMode = ProviderSelectionHostMode.Wizard;
|
||||||
|
|
||||||
public WelcomeWizardContext WizardContext { get; }
|
public WelcomeWizardContext WizardContext { get; }
|
||||||
|
|
||||||
@@ -74,6 +75,16 @@ public partial class ProviderSelectionPageViewModel : MailBaseViewModel
|
|||||||
{
|
{
|
||||||
base.OnNavigatedTo(mode, parameters);
|
base.OnNavigatedTo(mode, parameters);
|
||||||
|
|
||||||
|
var navigationContext = parameters as ProviderSelectionNavigationContext
|
||||||
|
?? ProviderSelectionNavigationContext.CreateForWizard();
|
||||||
|
|
||||||
|
_hostMode = navigationContext.HostMode;
|
||||||
|
|
||||||
|
if (mode != NavigationMode.Back)
|
||||||
|
{
|
||||||
|
WizardContext.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
Providers = _providerService.GetAvailableProviders();
|
Providers = _providerService.GetAvailableProviders();
|
||||||
AvailableColors = _themeService.GetAvailableAccountColors()
|
AvailableColors = _themeService.GetAvailableAccountColors()
|
||||||
.Select(hex => new AppColorViewModel(hex))
|
.Select(hex => new AppColorViewModel(hex))
|
||||||
@@ -135,8 +146,10 @@ public partial class ProviderSelectionPageViewModel : MailBaseViewModel
|
|||||||
|
|
||||||
if (WizardContext.IsGenericImap)
|
if (WizardContext.IsGenericImap)
|
||||||
{
|
{
|
||||||
// Navigate to ImapCalDavSettingsPage in wizard mode
|
var context = _hostMode == ProviderSelectionHostMode.SettingsAddAccount
|
||||||
var context = ImapCalDavSettingsNavigationContext.CreateForWizardMode(
|
? ImapCalDavSettingsNavigationContext.CreateForAddAccountMode(
|
||||||
|
WizardContext.BuildAccountCreationDialogResult())
|
||||||
|
: ImapCalDavSettingsNavigationContext.CreateForWizardMode(
|
||||||
WizardContext.BuildAccountCreationDialogResult());
|
WizardContext.BuildAccountCreationDialogResult());
|
||||||
|
|
||||||
Messenger.Send(new BreadcrumbNavigationRequested(
|
Messenger.Send(new BreadcrumbNavigationRequested(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using Wino.Core.Domain;
|
|||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.Navigation;
|
using Wino.Core.Domain.Models.Navigation;
|
||||||
|
using Wino.Core.Domain.Validation;
|
||||||
using Wino.Mail.ViewModels.Data;
|
using Wino.Mail.ViewModels.Data;
|
||||||
using Wino.Messaging.Client.Navigation;
|
using Wino.Messaging.Client.Navigation;
|
||||||
|
|
||||||
@@ -99,7 +100,7 @@ public partial class SpecialImapCredentialsPageViewModel : MailBaseViewModel
|
|||||||
{
|
{
|
||||||
CanProceed = !string.IsNullOrWhiteSpace(DisplayName)
|
CanProceed = !string.IsNullOrWhiteSpace(DisplayName)
|
||||||
&& !string.IsNullOrWhiteSpace(EmailAddress)
|
&& !string.IsNullOrWhiteSpace(EmailAddress)
|
||||||
&& EmailValidation.EmailValidator.Validate(EmailAddress ?? string.Empty)
|
&& MailAccountAddressValidator.IsValid(EmailAddress)
|
||||||
&& !string.IsNullOrWhiteSpace(AppSpecificPassword);
|
&& !string.IsNullOrWhiteSpace(AppSpecificPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using Wino.Core.Domain.Interfaces;
|
|||||||
using Wino.Core.Domain.Models.Accounts;
|
using Wino.Core.Domain.Models.Accounts;
|
||||||
using Wino.Core.Domain.Models.Navigation;
|
using Wino.Core.Domain.Models.Navigation;
|
||||||
using Wino.Core.Domain.Models.Updates;
|
using Wino.Core.Domain.Models.Updates;
|
||||||
|
using Wino.Mail.ViewModels.Data;
|
||||||
using Wino.Messaging.Client.Navigation;
|
using Wino.Messaging.Client.Navigation;
|
||||||
using Wino.Messaging.UI;
|
using Wino.Messaging.UI;
|
||||||
|
|
||||||
@@ -68,7 +69,8 @@ public partial class WelcomePageV2ViewModel : MailBaseViewModel
|
|||||||
{
|
{
|
||||||
Messenger.Send(new BreadcrumbNavigationRequested(
|
Messenger.Send(new BreadcrumbNavigationRequested(
|
||||||
Translator.WelcomeWizard_Step2Title,
|
Translator.WelcomeWizard_Step2Title,
|
||||||
WinoPage.ProviderSelectionPage));
|
WinoPage.ProviderSelectionPage,
|
||||||
|
ProviderSelectionNavigationContext.CreateForWizard()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand(CanExecute = nameof(CanOpenWelcomeActions))]
|
[RelayCommand(CanExecute = nameof(CanOpenWelcomeActions))]
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using Wino.Core.Domain;
|
|||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.Accounts;
|
using Wino.Core.Domain.Models.Accounts;
|
||||||
|
using Wino.Core.Domain.Validation;
|
||||||
using Wino.Core.ViewModels.Data;
|
using Wino.Core.ViewModels.Data;
|
||||||
using Wino.Helpers;
|
using Wino.Helpers;
|
||||||
|
|
||||||
@@ -208,7 +209,7 @@ public sealed partial class NewAccountDialog : ContentDialog
|
|||||||
&& !string.IsNullOrEmpty(AccountNameTextbox.Text)
|
&& !string.IsNullOrEmpty(AccountNameTextbox.Text)
|
||||||
&& (IsSpecialImapServerPartVisible ? (!string.IsNullOrEmpty(AppSpecificPassword.Password)
|
&& (IsSpecialImapServerPartVisible ? (!string.IsNullOrEmpty(AppSpecificPassword.Password)
|
||||||
&& !string.IsNullOrEmpty(DisplayNameTextBox.Text)
|
&& !string.IsNullOrEmpty(DisplayNameTextBox.Text)
|
||||||
&& EmailValidation.EmailValidator.Validate(SpecialImapAddress.Text)) : true);
|
&& MailAccountAddressValidator.IsValid(SpecialImapAddress.Text)) : true);
|
||||||
|
|
||||||
IsPrimaryButtonEnabled = shouldEnable;
|
IsPrimaryButtonEnabled = shouldEnable;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user