diff --git a/Wino.Core.Domain/Exceptions/ImapClientPoolException.cs b/Wino.Core.Domain/Exceptions/ImapClientPoolException.cs index ffb3bf14..f109d383 100644 --- a/Wino.Core.Domain/Exceptions/ImapClientPoolException.cs +++ b/Wino.Core.Domain/Exceptions/ImapClientPoolException.cs @@ -20,7 +20,7 @@ public class ImapClientPoolException : Exception ProtocolLog = protocolLog; } - public ImapClientPoolException(Exception innerException, string protocolLog) : base(Translator.Exception_ImapClientPoolFailed, innerException) + public ImapClientPoolException(Exception innerException, string protocolLog) : base(innerException.Message, innerException) { ProtocolLog = protocolLog; } diff --git a/Wino.Core.Domain/Translations/en_US/resources.json b/Wino.Core.Domain/Translations/en_US/resources.json index 6f4bbc69..2edde941 100644 --- a/Wino.Core.Domain/Translations/en_US/resources.json +++ b/Wino.Core.Domain/Translations/en_US/resources.json @@ -260,6 +260,9 @@ "ImapConnectionSecurity_SslTls": "SSL/TLS", "ImapConnectionSecurity_StartTls": "STARTTLS", "IMAPSetupDialog_AccountType": "Account type", + "IMAPSetupDialog_ValidationSuccess_Title": "Success", + "IMAPSetupDialog_ValidationSuccess_Message": "Validation successful", + "IMAPSetupDialog_ValidationFailed_Title": "IMAP Server validation failed.", "IMAPSetupDialog_CertificateAllowanceRequired_Row0": "This server is requesting a SSL handshake to continue. Please confirm the certificate details below.", "IMAPSetupDialog_CertificateAllowanceRequired_Row1": "Allow the handshake to continue setting up your account.", "IMAPSetupDialog_CertificateDenied": "User didn't authorize the handshake with the certificate.", @@ -274,6 +277,8 @@ "IMAPSetupDialog_DisplayNamePlaceholder": "eg. John Doe", "IMAPSetupDialog_IncomingMailServer": "Incoming mail server", "IMAPSetupDialog_IncomingMailServerPort": "Port", + "IMAPSetupDialog_IMAPSettings": "IMAP Server Settings", + "IMAPSetupDialog_SMTPSettings": "SMTP Server Settings", "IMAPSetupDialog_MailAddress": "Email address", "IMAPSetupDialog_MailAddressPlaceholder": "someone@example.com", "IMAPSetupDialog_OutgoingMailServer": "Outgoing (SMTP) mail server", @@ -459,6 +464,8 @@ "SearchingIn": "Searching in", "SearchPivotName": "Results", "SettingConfigureSpecialFolders_Button": "Configure", + "SettingsEditAccountDetails_IMAPConfiguration_Title": "IMAP/SMTP Configuration", + "SettingsEditAccountDetails_IMAPConfiguration_Description": "Change your incoming/outgoing server settings.", "SettingsAbout_Description": "Learn more about Wino.", "SettingsAbout_Title": "About", "SettingsAboutGithub_Description": "Go to issue tracker GitHub repository.", diff --git a/Wino.Core/Services/ImapTestService.cs b/Wino.Core/Services/ImapTestService.cs index ce20f6c9..437f72ce 100644 --- a/Wino.Core/Services/ImapTestService.cs +++ b/Wino.Core/Services/ImapTestService.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Threading.Tasks; using MailKit.Net.Smtp; using Wino.Core.Domain.Entities.Shared; @@ -38,30 +39,41 @@ public class ImapTestService : IImapTestService public async Task TestImapConnectionAsync(CustomServerInformation serverInformation, bool allowSSLHandShake) { - EnsureProtocolLogFileExists(); - - var poolOptions = ImapClientPoolOptions.CreateTestPool(serverInformation, _protocolLogStream); - - var clientPool = new ImapClientPool(poolOptions) + try { - ThrowOnSSLHandshakeCallback = !allowSSLHandShake - }; + EnsureProtocolLogFileExists(); - using (clientPool) - { - // This call will make sure that everything is authenticated + connected successfully. - var client = await clientPool.GetClientAsync(); + var poolOptions = ImapClientPoolOptions.CreateTestPool(serverInformation, _protocolLogStream); - clientPool.Release(client); + var clientPool = new ImapClientPool(poolOptions) + { + ThrowOnSSLHandshakeCallback = !allowSSLHandShake + }; + + using (clientPool) + { + // This call will make sure that everything is authenticated + connected successfully. + var client = await clientPool.GetClientAsync(); + + clientPool.Release(client); + } + + // Test SMTP connectivity. + using var smtpClient = new SmtpClient(); + + if (!smtpClient.IsConnected) + await smtpClient.ConnectAsync(serverInformation.OutgoingServer, int.Parse(serverInformation.OutgoingServerPort), MailKit.Security.SecureSocketOptions.Auto); + + if (!smtpClient.IsAuthenticated) + await smtpClient.AuthenticateAsync(serverInformation.OutgoingServerUsername, serverInformation.OutgoingServerPassword); + } + catch (Exception) + { + throw; + } + finally + { + _protocolLogStream?.Dispose(); } - - // Test SMTP connectivity. - using var smtpClient = new SmtpClient(); - - if (!smtpClient.IsConnected) - await smtpClient.ConnectAsync(serverInformation.OutgoingServer, int.Parse(serverInformation.OutgoingServerPort), MailKit.Security.SecureSocketOptions.Auto); - - if (!smtpClient.IsAuthenticated) - await smtpClient.AuthenticateAsync(serverInformation.OutgoingServerUsername, serverInformation.OutgoingServerPassword); } } diff --git a/Wino.Mail.ViewModels/EditAccountDetailsPageViewModel.cs b/Wino.Mail.ViewModels/EditAccountDetailsPageViewModel.cs index fb0a5b78..0c2e19e2 100644 --- a/Wino.Mail.ViewModels/EditAccountDetailsPageViewModel.cs +++ b/Wino.Mail.ViewModels/EditAccountDetailsPageViewModel.cs @@ -1,11 +1,14 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; +using Wino.Core.Domain; using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Accounts; using Wino.Core.Domain.Models.Navigation; using Wino.Core.ViewModels.Data; using Wino.Messaging.Client.Navigation; @@ -16,6 +19,8 @@ public partial class EditAccountDetailsPageViewModel : MailBaseViewModel { private readonly IAccountService _accountService; private readonly IThemeService _themeService; + private readonly IImapTestService _imapTestService; + private readonly IMailDialogService _mailDialogService; [ObservableProperty] public partial MailAccount Account { get; set; } @@ -29,14 +34,56 @@ public partial class EditAccountDetailsPageViewModel : MailBaseViewModel [ObservableProperty] public partial AppColorViewModel SelectedColor { get; set; } + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(IsImapServer))] + public partial CustomServerInformation ServerInformation { get; set; } [ObservableProperty] public partial List AvailableColors { get; set; } - public EditAccountDetailsPageViewModel(IAccountService accountService, IThemeService themeService) + + [ObservableProperty] + public partial int SelectedIncomingServerConnectionSecurityIndex { get; set; } + + [ObservableProperty] + public partial int SelectedIncomingServerAuthenticationMethodIndex { get; set; } + + [ObservableProperty] + public partial int SelectedOutgoingServerConnectionSecurityIndex { get; set; } + + [ObservableProperty] + public partial int SelectedOutgoingServerAuthenticationMethodIndex { get; set; } + + public List AvailableAuthenticationMethods { get; } = + [ + new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.Auto, Translator.ImapAuthenticationMethod_Auto), + new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.None, Translator.ImapAuthenticationMethod_None), + new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.NormalPassword, Translator.ImapAuthenticationMethod_Plain), + new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.EncryptedPassword, Translator.ImapAuthenticationMethod_EncryptedPassword), + new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.Ntlm, Translator.ImapAuthenticationMethod_Ntlm), + new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.CramMd5, Translator.ImapAuthenticationMethod_CramMD5), + new ImapAuthenticationMethodModel(Core.Domain.Enums.ImapAuthenticationMethod.DigestMd5, Translator.ImapAuthenticationMethod_DigestMD5) + ]; + + public List AvailableConnectionSecurities { get; set; } = + [ + new ImapConnectionSecurityModel(Core.Domain.Enums.ImapConnectionSecurity.Auto, Translator.ImapConnectionSecurity_Auto), + new ImapConnectionSecurityModel(Core.Domain.Enums.ImapConnectionSecurity.SslTls, Translator.ImapConnectionSecurity_SslTls), + new ImapConnectionSecurityModel(Core.Domain.Enums.ImapConnectionSecurity.StartTls, Translator.ImapConnectionSecurity_StartTls), + new ImapConnectionSecurityModel(Core.Domain.Enums.ImapConnectionSecurity.None, Translator.ImapConnectionSecurity_None) + ]; + + public bool IsImapServer => ServerInformation != null; + + public EditAccountDetailsPageViewModel(IAccountService accountService, + IThemeService themeService, + IImapTestService imapTestService, + IMailDialogService mailDialogService) { _accountService = accountService; _themeService = themeService; + _imapTestService = imapTestService; + _mailDialogService = mailDialogService; var colorHexList = _themeService.GetAvailableAccountColors(); @@ -51,12 +98,43 @@ public partial class EditAccountDetailsPageViewModel : MailBaseViewModel Messenger.Send(new BackBreadcrumNavigationRequested()); } + [RelayCommand] + private Task SaveWithoutGoBackAsync() + { + return UpdateAccountAsync(); + } + + [RelayCommand] + private async Task ValidateImapSettingsAsync() + { + try + { + await _imapTestService.TestImapConnectionAsync(ServerInformation, true); + _mailDialogService.InfoBarMessage(Translator.IMAPSetupDialog_ValidationSuccess_Title, Translator.IMAPSetupDialog_ValidationSuccess_Message, Core.Domain.Enums.InfoBarMessageType.Success); ; + } + catch (Exception ex) + { + _mailDialogService.InfoBarMessage(Translator.IMAPSetupDialog_ValidationFailed_Title, ex.Message, Core.Domain.Enums.InfoBarMessageType.Error); ; + } + } + private Task UpdateAccountAsync() { Account.Name = AccountName; Account.SenderName = SenderName; Account.AccountColorHex = SelectedColor == null ? string.Empty : SelectedColor.Hex; + if (ServerInformation != null) + { + ServerInformation.IncomingAuthenticationMethod = AvailableAuthenticationMethods[SelectedIncomingServerAuthenticationMethodIndex].ImapAuthenticationMethod; + ServerInformation.IncomingServerSocketOption = AvailableConnectionSecurities[SelectedIncomingServerConnectionSecurityIndex].ImapConnectionSecurity; + + ServerInformation.OutgoingAuthenticationMethod = AvailableAuthenticationMethods[SelectedOutgoingServerAuthenticationMethodIndex].ImapAuthenticationMethod; + ServerInformation.OutgoingServerSocketOption = AvailableConnectionSecurities[SelectedOutgoingServerConnectionSecurityIndex].ImapConnectionSecurity; + + Account.ServerInformation = ServerInformation; + } + return _accountService.UpdateAccountAsync(Account); } @@ -78,11 +156,21 @@ public partial class EditAccountDetailsPageViewModel : MailBaseViewModel Account = account; AccountName = account.Name; SenderName = account.SenderName; + ServerInformation = Account.ServerInformation; if (!string.IsNullOrEmpty(account.AccountColorHex)) { SelectedColor = AvailableColors.FirstOrDefault(a => a.Hex == account.AccountColorHex); } + + if (ServerInformation != null) + { + SelectedIncomingServerAuthenticationMethodIndex = AvailableAuthenticationMethods.FindIndex(a => a.ImapAuthenticationMethod == ServerInformation.IncomingAuthenticationMethod); + SelectedIncomingServerConnectionSecurityIndex = AvailableConnectionSecurities.FindIndex(a => a.ImapConnectionSecurity == ServerInformation.IncomingServerSocketOption); + + SelectedOutgoingServerAuthenticationMethodIndex = AvailableAuthenticationMethods.FindIndex(a => a.ImapAuthenticationMethod == ServerInformation.OutgoingAuthenticationMethod); + SelectedOutgoingServerConnectionSecurityIndex = AvailableConnectionSecurities.FindIndex(a => a.ImapConnectionSecurity == ServerInformation.OutgoingServerSocketOption); + } } } } diff --git a/Wino.Mail/Views/Settings/EditAccountDetailsPage.xaml b/Wino.Mail/Views/Settings/EditAccountDetailsPage.xaml index 625c6586..3fb13c6a 100644 --- a/Wino.Mail/Views/Settings/EditAccountDetailsPage.xaml +++ b/Wino.Mail/Views/Settings/EditAccountDetailsPage.xaml @@ -9,6 +9,7 @@ xmlns:data="using:Wino.Core.ViewModels.Data" xmlns:domain="using:Wino.Core.Domain" xmlns:helpers="using:Wino.Helpers" + xmlns:imapsetup="using:Wino.Views.ImapSetup" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:picker="using:Microsoft.UI.Xaml.Controls" mc:Ignorable="d"> @@ -72,6 +73,158 @@ Content="{x:Bind domain:Translator.Buttons_Reset}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +