Bunch of calendar implementation thing.
This commit is contained in:
@@ -2,4 +2,9 @@
|
|||||||
|
|
||||||
namespace Wino.Core.Domain.Models.Accounts;
|
namespace Wino.Core.Domain.Models.Accounts;
|
||||||
|
|
||||||
public record SpecialImapProviderDetails(string Address, string Password, string SenderName, SpecialImapProvider SpecialImapProvider);
|
public record SpecialImapProviderDetails(
|
||||||
|
string Address,
|
||||||
|
string Password,
|
||||||
|
string SenderName,
|
||||||
|
SpecialImapProvider SpecialImapProvider,
|
||||||
|
ImapCalendarSupportMode CalendarSupportMode = ImapCalendarSupportMode.CalDav);
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ public static class GoogleIntegratorExtensions
|
|||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AccountCalendar AsCalendar(this CalendarListEntry calendarListEntry, Guid accountId)
|
public static AccountCalendar AsCalendar(this CalendarListEntry calendarListEntry, Guid accountId, string fallbackBackgroundColor = null)
|
||||||
{
|
{
|
||||||
var calendar = new AccountCalendar()
|
var calendar = new AccountCalendar()
|
||||||
{
|
{
|
||||||
@@ -147,7 +147,9 @@ public static class GoogleIntegratorExtensions
|
|||||||
// Bg color must present. Generate one if doesnt exists.
|
// Bg color must present. Generate one if doesnt exists.
|
||||||
// Text color is optional. It'll be overriden by UI for readibility.
|
// Text color is optional. It'll be overriden by UI for readibility.
|
||||||
|
|
||||||
calendar.BackgroundColorHex = string.IsNullOrEmpty(calendarListEntry.BackgroundColor) ? ColorHelpers.GenerateFlatColorHex() : calendarListEntry.BackgroundColor;
|
calendar.BackgroundColorHex = string.IsNullOrEmpty(calendarListEntry.BackgroundColor)
|
||||||
|
? fallbackBackgroundColor ?? ColorHelpers.GenerateFlatColorHex()
|
||||||
|
: calendarListEntry.BackgroundColor;
|
||||||
calendar.TextColorHex = string.IsNullOrEmpty(calendarListEntry.ForegroundColor) ? "#000000" : calendarListEntry.ForegroundColor;
|
calendar.TextColorHex = string.IsNullOrEmpty(calendarListEntry.ForegroundColor) ? "#000000" : calendarListEntry.ForegroundColor;
|
||||||
|
|
||||||
return calendar;
|
return calendar;
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ public static class OutlookIntegratorExtensions
|
|||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AccountCalendar AsCalendar(this Calendar outlookCalendar, MailAccount assignedAccount)
|
public static AccountCalendar AsCalendar(this Calendar outlookCalendar, MailAccount assignedAccount, string fallbackBackgroundColor = null)
|
||||||
{
|
{
|
||||||
var calendar = new AccountCalendar()
|
var calendar = new AccountCalendar()
|
||||||
{
|
{
|
||||||
@@ -191,7 +191,9 @@ public static class OutlookIntegratorExtensions
|
|||||||
// Bg must be present. Generate flat one if doesn't exists.
|
// Bg must be present. Generate flat one if doesn't exists.
|
||||||
// Text doesnt exists for Outlook.
|
// Text doesnt exists for Outlook.
|
||||||
|
|
||||||
calendar.BackgroundColorHex = string.IsNullOrEmpty(outlookCalendar.HexColor) ? ColorHelpers.GenerateFlatColorHex() : outlookCalendar.HexColor;
|
calendar.BackgroundColorHex = string.IsNullOrEmpty(outlookCalendar.HexColor)
|
||||||
|
? fallbackBackgroundColor ?? ColorHelpers.GenerateFlatColorHex()
|
||||||
|
: outlookCalendar.HexColor;
|
||||||
calendar.TextColorHex = "#000000";
|
calendar.TextColorHex = "#000000";
|
||||||
|
|
||||||
return calendar;
|
return calendar;
|
||||||
|
|||||||
@@ -1,49 +1,115 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace Wino.Core.Misc;
|
namespace Wino.Core.Misc;
|
||||||
|
|
||||||
public static class ColorHelpers
|
public static class ColorHelpers
|
||||||
{
|
{
|
||||||
public static string GenerateFlatColorHex()
|
private static readonly string[] FlatUiColorPalette =
|
||||||
|
[
|
||||||
|
"#B91C1C", "#C2410C", "#B45309", "#A16207", "#4D7C0F", "#15803D", "#047857", "#0F766E", "#0E7490", "#0369A1",
|
||||||
|
"#1D4ED8", "#4338CA", "#6D28D9", "#7E22CE", "#A21CAF", "#BE185D", "#E11D48", "#DC2626", "#EA580C", "#D97706",
|
||||||
|
"#CA8A04", "#65A30D", "#16A34A", "#059669", "#0D9488", "#0891B2", "#0284C7", "#2563EB", "#4F46E5", "#7C3AED",
|
||||||
|
"#9333EA", "#C026D3", "#DB2777", "#F43F5E", "#EF4444", "#F97316", "#F59E0B", "#EAB308", "#84CC16", "#22C55E",
|
||||||
|
"#10B981", "#14B8A6", "#06B6D4", "#0EA5E9", "#3B82F6", "#6366F1", "#8B5CF6", "#A855F7", "#D946EF", "#EC4899",
|
||||||
|
"#FB7185", "#F87171", "#FB923C", "#FBBF24", "#FACC15", "#A3E635", "#4ADE80", "#34D399", "#2DD4BF", "#22D3EE",
|
||||||
|
"#38BDF8", "#60A5FA", "#818CF8", "#A78BFA", "#C084FC", "#E879F9", "#F472B6", "#FDA4AF", "#FCA5A5", "#FDBA74",
|
||||||
|
"#FCD34D", "#FDE047", "#BEF264", "#86EFAC", "#6EE7B7", "#5EEAD4", "#67E8F9", "#7DD3FC", "#93C5FD", "#A5B4FC",
|
||||||
|
"#C4B5FD", "#D8B4FE", "#F0ABFC", "#F9A8D4", "#A16207", "#9A3412", "#7C2D12", "#6F1D1B", "#7F1D1D", "#881337",
|
||||||
|
"#831843", "#701A75", "#581C87", "#312E81", "#1E3A8A", "#1D4ED8", "#155E75", "#134E4A", "#14532D", "#3F6212",
|
||||||
|
"#365314", "#3F3F46", "#52525B", "#57534E", "#44403C", "#78716C", "#6B7280", "#4B5563", "#374151", "#1F2937",
|
||||||
|
"#A16207", "#B45309", "#C2410C", "#9F1239", "#BE123C", "#C026D3", "#7E22CE", "#6D28D9", "#4338CA", "#1D4ED8"
|
||||||
|
];
|
||||||
|
|
||||||
|
public static IReadOnlyList<string> GetFlatColorPalette() => FlatUiColorPalette;
|
||||||
|
|
||||||
|
public static string GenerateFlatColorHex() => GetDistinctFlatColorHex(Array.Empty<string>());
|
||||||
|
|
||||||
|
public static string GetDistinctFlatColorHex(IEnumerable<string> usedColors)
|
||||||
{
|
{
|
||||||
Random random = new();
|
var used = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
int hue = random.Next(0, 360); // Full hue range
|
|
||||||
int saturation = 70 + random.Next(30); // High saturation (70-100%)
|
|
||||||
int lightness = 50 + random.Next(20); // Bright colors (50-70%)
|
|
||||||
|
|
||||||
var color = FromHsl(hue, saturation, lightness);
|
if (usedColors != null)
|
||||||
|
{
|
||||||
|
foreach (var color in usedColors)
|
||||||
|
{
|
||||||
|
if (TryNormalizeHexColor(color, out var normalized))
|
||||||
|
{
|
||||||
|
used.Add(normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ToHexString(color);
|
foreach (var color in FlatUiColorPalette)
|
||||||
|
{
|
||||||
|
if (!used.Contains(color))
|
||||||
|
{
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var attempt = 0;
|
||||||
|
while (attempt < 500)
|
||||||
|
{
|
||||||
|
var baseColor = FlatUiColorPalette[attempt % FlatUiColorPalette.Length];
|
||||||
|
var cycle = (attempt / FlatUiColorPalette.Length) + 1;
|
||||||
|
var candidate = AdjustColor(baseColor, cycle);
|
||||||
|
|
||||||
|
if (!used.Contains(candidate))
|
||||||
|
{
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
attempt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "#5C7A8A";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ToHexString(this Color c) => $"#{c.R:X2}{c.G:X2}{c.B:X2}";
|
public static string ToHexString(this Color c) => $"#{c.R:X2}{c.G:X2}{c.B:X2}";
|
||||||
|
|
||||||
public static string ToRgbString(this Color c) => $"RGB({c.R}, {c.G}, {c.B})";
|
public static string ToRgbString(this Color c) => $"RGB({c.R}, {c.G}, {c.B})";
|
||||||
|
|
||||||
private static Color FromHsl(int h, int s, int l)
|
private static bool TryNormalizeHexColor(string value, out string normalized)
|
||||||
{
|
{
|
||||||
double hue = h / 360.0;
|
normalized = null;
|
||||||
double saturation = s / 100.0;
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
double lightness = l / 100.0;
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Conversion from HSL to RGB
|
var color = value.Trim();
|
||||||
var chroma = (1 - Math.Abs(2 * lightness - 1)) * saturation;
|
if (color.StartsWith('#'))
|
||||||
var x = chroma * (1 - Math.Abs((hue * 6) % 2 - 1));
|
{
|
||||||
var m = lightness - chroma / 2;
|
color = color[1..];
|
||||||
|
}
|
||||||
|
|
||||||
double r = 0, g = 0, b = 0;
|
if (color.Length != 6)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (hue < 1.0 / 6.0) { r = chroma; g = x; b = 0; }
|
if (!int.TryParse(color, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _))
|
||||||
else if (hue < 2.0 / 6.0) { r = x; g = chroma; b = 0; }
|
{
|
||||||
else if (hue < 3.0 / 6.0) { r = 0; g = chroma; b = x; }
|
return false;
|
||||||
else if (hue < 4.0 / 6.0) { r = 0; g = x; b = chroma; }
|
}
|
||||||
else if (hue < 5.0 / 6.0) { r = x; g = 0; b = chroma; }
|
|
||||||
else { r = chroma; g = 0; b = x; }
|
|
||||||
|
|
||||||
return Color.FromArgb(
|
normalized = $"#{color.ToUpperInvariant()}";
|
||||||
(int)((r + m) * 255),
|
return true;
|
||||||
(int)((g + m) * 255),
|
}
|
||||||
(int)((b + m) * 255));
|
|
||||||
|
private static string AdjustColor(string hexColor, int cycle)
|
||||||
|
{
|
||||||
|
var color = ColorTranslator.FromHtml(hexColor);
|
||||||
|
var factor = Math.Max(0.55, 1.0 - (cycle * 0.08));
|
||||||
|
|
||||||
|
var adjusted = Color.FromArgb(
|
||||||
|
(int)Math.Clamp(color.R * factor, 0, 255),
|
||||||
|
(int)Math.Clamp(color.G * factor, 0, 255),
|
||||||
|
(int)Math.Clamp(color.B * factor, 0, 255));
|
||||||
|
|
||||||
|
return adjusted.ToHexString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,18 +9,20 @@ public class ImapRequest
|
|||||||
{
|
{
|
||||||
public Func<IImapClient, IRequestBase, Task> IntegratorTask { get; }
|
public Func<IImapClient, IRequestBase, Task> IntegratorTask { get; }
|
||||||
public IRequestBase Request { get; }
|
public IRequestBase Request { get; }
|
||||||
|
public bool RequiresConnectedClient { get; }
|
||||||
|
|
||||||
public ImapRequest(Func<IImapClient, IRequestBase, Task> integratorTask, IRequestBase request)
|
public ImapRequest(Func<IImapClient, IRequestBase, Task> integratorTask, IRequestBase request, bool requiresConnectedClient = true)
|
||||||
{
|
{
|
||||||
IntegratorTask = integratorTask;
|
IntegratorTask = integratorTask;
|
||||||
Request = request;
|
Request = request;
|
||||||
|
RequiresConnectedClient = requiresConnectedClient;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ImapRequest<TRequestBaseType> : ImapRequest where TRequestBaseType : IRequestBase
|
public class ImapRequest<TRequestBaseType> : ImapRequest where TRequestBaseType : IRequestBase
|
||||||
{
|
{
|
||||||
public ImapRequest(Func<IImapClient, TRequestBaseType, Task> integratorTask, TRequestBaseType request)
|
public ImapRequest(Func<IImapClient, TRequestBaseType, Task> integratorTask, TRequestBaseType request, bool requiresConnectedClient = true)
|
||||||
: base((client, request) => integratorTask(client, (TRequestBaseType)request), request)
|
: base((client, request) => integratorTask(client, (TRequestBaseType)request), request, requiresConnectedClient)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public class SynchronizerFactory : ISynchronizerFactory
|
|||||||
private readonly UnifiedImapSynchronizer _unifiedImapSynchronizer;
|
private readonly UnifiedImapSynchronizer _unifiedImapSynchronizer;
|
||||||
private readonly ICalDavClient _calDavClient;
|
private readonly ICalDavClient _calDavClient;
|
||||||
private readonly IAutoDiscoveryService _autoDiscoveryService;
|
private readonly IAutoDiscoveryService _autoDiscoveryService;
|
||||||
|
private readonly ICalendarService _calendarService;
|
||||||
|
|
||||||
private readonly List<IWinoSynchronizerBase> synchronizerCache = new();
|
private readonly List<IWinoSynchronizerBase> synchronizerCache = new();
|
||||||
|
|
||||||
@@ -39,7 +40,8 @@ public class SynchronizerFactory : ISynchronizerFactory
|
|||||||
IImapSynchronizerErrorHandlerFactory imapSynchronizerErrorHandlerFactory,
|
IImapSynchronizerErrorHandlerFactory imapSynchronizerErrorHandlerFactory,
|
||||||
UnifiedImapSynchronizer unifiedImapSynchronizer,
|
UnifiedImapSynchronizer unifiedImapSynchronizer,
|
||||||
ICalDavClient calDavClient,
|
ICalDavClient calDavClient,
|
||||||
IAutoDiscoveryService autoDiscoveryService)
|
IAutoDiscoveryService autoDiscoveryService,
|
||||||
|
ICalendarService calendarService)
|
||||||
{
|
{
|
||||||
_outlookChangeProcessor = outlookChangeProcessor;
|
_outlookChangeProcessor = outlookChangeProcessor;
|
||||||
_gmailChangeProcessor = gmailChangeProcessor;
|
_gmailChangeProcessor = gmailChangeProcessor;
|
||||||
@@ -53,6 +55,7 @@ public class SynchronizerFactory : ISynchronizerFactory
|
|||||||
_unifiedImapSynchronizer = unifiedImapSynchronizer;
|
_unifiedImapSynchronizer = unifiedImapSynchronizer;
|
||||||
_calDavClient = calDavClient;
|
_calDavClient = calDavClient;
|
||||||
_autoDiscoveryService = autoDiscoveryService;
|
_autoDiscoveryService = autoDiscoveryService;
|
||||||
|
_calendarService = calendarService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IWinoSynchronizerBase> GetAccountSynchronizerAsync(Guid accountId)
|
public async Task<IWinoSynchronizerBase> GetAccountSynchronizerAsync(Guid accountId)
|
||||||
@@ -88,7 +91,7 @@ public class SynchronizerFactory : ISynchronizerFactory
|
|||||||
var gmailAuthenticator = _authenticationProvider.GetAuthenticator(Domain.Enums.MailProviderType.Gmail) as IGmailAuthenticator;
|
var gmailAuthenticator = _authenticationProvider.GetAuthenticator(Domain.Enums.MailProviderType.Gmail) as IGmailAuthenticator;
|
||||||
return new GmailSynchronizer(mailAccount, gmailAuthenticator, _gmailChangeProcessor, _gmailSynchronizerErrorHandlerFactory);
|
return new GmailSynchronizer(mailAccount, gmailAuthenticator, _gmailChangeProcessor, _gmailSynchronizerErrorHandlerFactory);
|
||||||
case Domain.Enums.MailProviderType.IMAP4:
|
case Domain.Enums.MailProviderType.IMAP4:
|
||||||
return new ImapSynchronizer(mailAccount, _imapChangeProcessor, _applicationConfiguration, _unifiedImapSynchronizer, _imapSynchronizerErrorHandlerFactory, _calDavClient, _autoDiscoveryService);
|
return new ImapSynchronizer(mailAccount, _imapChangeProcessor, _applicationConfiguration, _unifiedImapSynchronizer, _imapSynchronizerErrorHandlerFactory, _calDavClient, _autoDiscoveryService, _calendarService);
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ using Wino.Core.Domain.Models.Synchronization;
|
|||||||
using Wino.Core.Extensions;
|
using Wino.Core.Extensions;
|
||||||
using Wino.Core.Http;
|
using Wino.Core.Http;
|
||||||
using Wino.Core.Integration.Processors;
|
using Wino.Core.Integration.Processors;
|
||||||
|
using Wino.Core.Misc;
|
||||||
using Wino.Core.Requests.Bundles;
|
using Wino.Core.Requests.Bundles;
|
||||||
using Wino.Core.Requests.Calendar;
|
using Wino.Core.Requests.Calendar;
|
||||||
using Wino.Core.Requests.Folder;
|
using Wino.Core.Requests.Folder;
|
||||||
@@ -583,6 +584,11 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
|
|
||||||
var localCalendars = await _gmailChangeProcessor.GetAccountCalendarsAsync(Account.Id).ConfigureAwait(false);
|
var localCalendars = await _gmailChangeProcessor.GetAccountCalendarsAsync(Account.Id).ConfigureAwait(false);
|
||||||
var remotePrimaryCalendarId = GetPrimaryCalendarId(calendarListResponse.Items);
|
var remotePrimaryCalendarId = GetPrimaryCalendarId(calendarListResponse.Items);
|
||||||
|
var usedCalendarColors = new HashSet<string>(
|
||||||
|
localCalendars
|
||||||
|
.Select(a => a.BackgroundColorHex)
|
||||||
|
.Where(a => !string.IsNullOrWhiteSpace(a)),
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
List<AccountCalendar> insertedCalendars = new();
|
List<AccountCalendar> insertedCalendars = new();
|
||||||
List<AccountCalendar> updatedCalendars = new();
|
List<AccountCalendar> updatedCalendars = new();
|
||||||
@@ -612,8 +618,12 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
if (existingLocalCalendar == null)
|
if (existingLocalCalendar == null)
|
||||||
{
|
{
|
||||||
// Insert new calendar.
|
// Insert new calendar.
|
||||||
var localCalendar = calendar.AsCalendar(Account.Id);
|
var fallbackColor = ColorHelpers.GetDistinctFlatColorHex(usedCalendarColors);
|
||||||
|
var localCalendar = calendar.AsCalendar(Account.Id, fallbackColor);
|
||||||
localCalendar.IsPrimary = string.Equals(localCalendar.RemoteCalendarId, remotePrimaryCalendarId, StringComparison.OrdinalIgnoreCase);
|
localCalendar.IsPrimary = string.Equals(localCalendar.RemoteCalendarId, remotePrimaryCalendarId, StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (string.IsNullOrWhiteSpace(localCalendar.BackgroundColorHex) || usedCalendarColors.Contains(localCalendar.BackgroundColorHex))
|
||||||
|
localCalendar.BackgroundColorHex = ColorHelpers.GetDistinctFlatColorHex(usedCalendarColors);
|
||||||
|
usedCalendarColors.Add(localCalendar.BackgroundColorHex);
|
||||||
insertedCalendars.Add(localCalendar);
|
insertedCalendars.Add(localCalendar);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ using Wino.Core.Extensions;
|
|||||||
using Wino.Core.Integration;
|
using Wino.Core.Integration;
|
||||||
using Wino.Core.Integration.Processors;
|
using Wino.Core.Integration.Processors;
|
||||||
using Wino.Core.Requests.Bundles;
|
using Wino.Core.Requests.Bundles;
|
||||||
|
using Wino.Core.Requests.Calendar;
|
||||||
using Wino.Core.Requests.Folder;
|
using Wino.Core.Requests.Folder;
|
||||||
using Wino.Core.Requests.Mail;
|
using Wino.Core.Requests.Mail;
|
||||||
using Wino.Core.Synchronizers.ImapSync;
|
using Wino.Core.Synchronizers.ImapSync;
|
||||||
@@ -63,9 +64,12 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
|||||||
private readonly IImapSynchronizerErrorHandlerFactory _errorHandlerFactory;
|
private readonly IImapSynchronizerErrorHandlerFactory _errorHandlerFactory;
|
||||||
private readonly ICalDavClient _calDavClient;
|
private readonly ICalDavClient _calDavClient;
|
||||||
private readonly IAutoDiscoveryService _autoDiscoveryService;
|
private readonly IAutoDiscoveryService _autoDiscoveryService;
|
||||||
|
private readonly ICalendarService _calendarService;
|
||||||
private readonly SemaphoreSlim _calDavDiscoveryLock = new(1, 1);
|
private readonly SemaphoreSlim _calDavDiscoveryLock = new(1, 1);
|
||||||
private Uri _cachedCalDavServiceUri;
|
private Uri _cachedCalDavServiceUri;
|
||||||
private bool _isCalDavDiscoveryAttempted;
|
private bool _isCalDavDiscoveryAttempted;
|
||||||
|
private readonly IImapCalendarOperationHandler _localCalendarOperationHandler;
|
||||||
|
private readonly IImapCalendarOperationHandler _calDavCalendarOperationHandler;
|
||||||
|
|
||||||
public ImapSynchronizer(MailAccount account,
|
public ImapSynchronizer(MailAccount account,
|
||||||
IImapChangeProcessor imapChangeProcessor,
|
IImapChangeProcessor imapChangeProcessor,
|
||||||
@@ -73,7 +77,8 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
|||||||
UnifiedImapSynchronizer unifiedSynchronizer,
|
UnifiedImapSynchronizer unifiedSynchronizer,
|
||||||
IImapSynchronizerErrorHandlerFactory errorHandlerFactory,
|
IImapSynchronizerErrorHandlerFactory errorHandlerFactory,
|
||||||
ICalDavClient calDavClient,
|
ICalDavClient calDavClient,
|
||||||
IAutoDiscoveryService autoDiscoveryService) : base(account, WeakReferenceMessenger.Default)
|
IAutoDiscoveryService autoDiscoveryService,
|
||||||
|
ICalendarService calendarService) : base(account, WeakReferenceMessenger.Default)
|
||||||
{
|
{
|
||||||
// Create client pool with account protocol log.
|
// Create client pool with account protocol log.
|
||||||
_imapChangeProcessor = imapChangeProcessor;
|
_imapChangeProcessor = imapChangeProcessor;
|
||||||
@@ -82,11 +87,14 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
|||||||
_errorHandlerFactory = errorHandlerFactory;
|
_errorHandlerFactory = errorHandlerFactory;
|
||||||
_calDavClient = calDavClient;
|
_calDavClient = calDavClient;
|
||||||
_autoDiscoveryService = autoDiscoveryService;
|
_autoDiscoveryService = autoDiscoveryService;
|
||||||
|
_calendarService = calendarService;
|
||||||
|
|
||||||
var protocolLogStream = CreateAccountProtocolLogFileStream();
|
var protocolLogStream = CreateAccountProtocolLogFileStream();
|
||||||
var poolOptions = ImapClientPoolOptions.CreateDefault(Account.ServerInformation, protocolLogStream);
|
var poolOptions = ImapClientPoolOptions.CreateDefault(Account.ServerInformation, protocolLogStream);
|
||||||
|
|
||||||
_clientPool = new ImapClientPool(poolOptions);
|
_clientPool = new ImapClientPool(poolOptions);
|
||||||
|
_localCalendarOperationHandler = new LocalCalendarOperationHandler(Account, _imapChangeProcessor, _calendarService, "local");
|
||||||
|
_calDavCalendarOperationHandler = new CalDavCalendarOperationHandler(Account, _imapChangeProcessor, _calendarService);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream CreateAccountProtocolLogFileStream()
|
private Stream CreateAccountProtocolLogFileStream()
|
||||||
@@ -319,6 +327,87 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
|||||||
}, request, request);
|
}, request, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override List<IRequestBundle<ImapRequest>> CreateCalendarEvent(CreateCalendarEventRequest request)
|
||||||
|
{
|
||||||
|
var handler = ResolveCalendarOperationHandler();
|
||||||
|
return CreateCalendarOperationTaskBundle(
|
||||||
|
request,
|
||||||
|
async value => await handler.CreateCalendarEventAsync(value).ConfigureAwait(false),
|
||||||
|
handler.RequiresConnectedClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<IRequestBundle<ImapRequest>> UpdateCalendarEvent(UpdateCalendarEventRequest request)
|
||||||
|
{
|
||||||
|
var handler = ResolveCalendarOperationHandler();
|
||||||
|
return CreateCalendarOperationTaskBundle(
|
||||||
|
request,
|
||||||
|
async value => await handler.UpdateCalendarEventAsync(value).ConfigureAwait(false),
|
||||||
|
handler.RequiresConnectedClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<IRequestBundle<ImapRequest>> DeleteCalendarEvent(DeleteCalendarEventRequest request)
|
||||||
|
{
|
||||||
|
var handler = ResolveCalendarOperationHandler();
|
||||||
|
return CreateCalendarOperationTaskBundle(
|
||||||
|
request,
|
||||||
|
async value => await handler.DeleteCalendarEventAsync(value).ConfigureAwait(false),
|
||||||
|
handler.RequiresConnectedClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<IRequestBundle<ImapRequest>> AcceptEvent(AcceptEventRequest request)
|
||||||
|
{
|
||||||
|
var handler = ResolveCalendarOperationHandler();
|
||||||
|
return CreateCalendarOperationTaskBundle(
|
||||||
|
request,
|
||||||
|
async value => await handler.AcceptEventAsync(value).ConfigureAwait(false),
|
||||||
|
handler.RequiresConnectedClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<IRequestBundle<ImapRequest>> DeclineEvent(DeclineEventRequest request)
|
||||||
|
{
|
||||||
|
var handler = ResolveCalendarOperationHandler();
|
||||||
|
return CreateCalendarOperationTaskBundle(
|
||||||
|
request,
|
||||||
|
async value => await handler.DeclineEventAsync(value).ConfigureAwait(false),
|
||||||
|
handler.RequiresConnectedClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<IRequestBundle<ImapRequest>> TentativeEvent(TentativeEventRequest request)
|
||||||
|
{
|
||||||
|
var handler = ResolveCalendarOperationHandler();
|
||||||
|
return CreateCalendarOperationTaskBundle(
|
||||||
|
request,
|
||||||
|
async value => await handler.TentativeEventAsync(value).ConfigureAwait(false),
|
||||||
|
handler.RequiresConnectedClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IImapCalendarOperationHandler ResolveCalendarOperationHandler()
|
||||||
|
{
|
||||||
|
var mode = Account.ServerInformation?.CalendarSupportMode ?? ImapCalendarSupportMode.Disabled;
|
||||||
|
|
||||||
|
return mode switch
|
||||||
|
{
|
||||||
|
ImapCalendarSupportMode.LocalOnly => _localCalendarOperationHandler,
|
||||||
|
ImapCalendarSupportMode.CalDav => _calDavCalendarOperationHandler,
|
||||||
|
_ => throw new NotSupportedException("Calendar operations are disabled for this IMAP account.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<IRequestBundle<ImapRequest>> CreateCalendarOperationTaskBundle<TRequest>(
|
||||||
|
TRequest request,
|
||||||
|
Func<TRequest, Task> operation,
|
||||||
|
bool requiresConnectedClient)
|
||||||
|
where TRequest : IRequestBase, IUIChangeRequest
|
||||||
|
{
|
||||||
|
return
|
||||||
|
[
|
||||||
|
new ImapRequestBundle(
|
||||||
|
new ImapRequest<TRequest>((client, value) => operation(value), request, requiresConnectedClient),
|
||||||
|
request,
|
||||||
|
request)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public override async Task<List<NewMailItemPackage>> CreateNewMailPackagesAsync(ImapMessageCreationPackage message, MailItemFolder assignedFolder, CancellationToken cancellationToken = default)
|
public override async Task<List<NewMailItemPackage>> CreateNewMailPackagesAsync(ImapMessageCreationPackage message, MailItemFolder assignedFolder, CancellationToken cancellationToken = default)
|
||||||
@@ -635,7 +724,10 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
executorClient = await _clientPool.GetClientAsync();
|
if (item.NativeRequest.RequiresConnectedClient)
|
||||||
|
{
|
||||||
|
executorClient = await _clientPool.GetClientAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (ImapClientPoolException)
|
catch (ImapClientPoolException)
|
||||||
{
|
{
|
||||||
@@ -682,7 +774,10 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_clientPool.Release(executorClient);
|
if (executorClient != null)
|
||||||
|
{
|
||||||
|
_clientPool.Release(executorClient);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1240,6 +1335,11 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
|||||||
var remoteCalendarsById = remoteCalendars
|
var remoteCalendarsById = remoteCalendars
|
||||||
.GroupBy(c => c.RemoteCalendarId, StringComparer.OrdinalIgnoreCase)
|
.GroupBy(c => c.RemoteCalendarId, StringComparer.OrdinalIgnoreCase)
|
||||||
.ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
|
.ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
|
||||||
|
var usedCalendarColors = new HashSet<string>(
|
||||||
|
localCalendars
|
||||||
|
.Select(a => a.BackgroundColorHex)
|
||||||
|
.Where(a => !string.IsNullOrWhiteSpace(a)),
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var remotePrimaryCalendarId = remoteCalendars.FirstOrDefault()?.RemoteCalendarId;
|
var remotePrimaryCalendarId = remoteCalendars.FirstOrDefault()?.RemoteCalendarId;
|
||||||
|
|
||||||
@@ -1274,11 +1374,12 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
|||||||
IsSynchronizationEnabled = true,
|
IsSynchronizationEnabled = true,
|
||||||
IsExtended = true,
|
IsExtended = true,
|
||||||
TextColorHex = "#000000",
|
TextColorHex = "#000000",
|
||||||
BackgroundColorHex = ColorHelpers.GenerateFlatColorHex(),
|
BackgroundColorHex = ColorHelpers.GetDistinctFlatColorHex(usedCalendarColors),
|
||||||
TimeZone = "UTC",
|
TimeZone = "UTC",
|
||||||
SynchronizationDeltaToken = string.Empty
|
SynchronizationDeltaToken = string.Empty
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usedCalendarColors.Add(newCalendar.BackgroundColorHex);
|
||||||
await _imapChangeProcessor.InsertAccountCalendarAsync(newCalendar).ConfigureAwait(false);
|
await _imapChangeProcessor.InsertAccountCalendarAsync(newCalendar).ConfigureAwait(false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1295,6 +1396,254 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private interface IImapCalendarOperationHandler
|
||||||
|
{
|
||||||
|
bool RequiresConnectedClient { get; }
|
||||||
|
Task CreateCalendarEventAsync(CreateCalendarEventRequest request);
|
||||||
|
Task UpdateCalendarEventAsync(UpdateCalendarEventRequest request);
|
||||||
|
Task DeleteCalendarEventAsync(DeleteCalendarEventRequest request);
|
||||||
|
Task AcceptEventAsync(AcceptEventRequest request);
|
||||||
|
Task DeclineEventAsync(DeclineEventRequest request);
|
||||||
|
Task TentativeEventAsync(TentativeEventRequest request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LocalCalendarOperationHandler : IImapCalendarOperationHandler
|
||||||
|
{
|
||||||
|
private readonly MailAccount _account;
|
||||||
|
private readonly IImapChangeProcessor _changeProcessor;
|
||||||
|
private readonly ICalendarService _calendarService;
|
||||||
|
private readonly string _resourceScheme;
|
||||||
|
|
||||||
|
public bool RequiresConnectedClient => false;
|
||||||
|
|
||||||
|
public LocalCalendarOperationHandler(MailAccount account, IImapChangeProcessor changeProcessor, ICalendarService calendarService, string resourceScheme)
|
||||||
|
{
|
||||||
|
_account = account;
|
||||||
|
_changeProcessor = changeProcessor;
|
||||||
|
_calendarService = calendarService;
|
||||||
|
_resourceScheme = resourceScheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateCalendarEventAsync(CreateCalendarEventRequest request)
|
||||||
|
{
|
||||||
|
var item = request.Item;
|
||||||
|
EnsureCalendarItemDefaults(item, _account, "local");
|
||||||
|
item.AssignedCalendar ??= await _calendarService.GetAccountCalendarAsync(item.CalendarId).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var existing = await _calendarService.GetCalendarItemAsync(item.Id).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (existing == null)
|
||||||
|
await _calendarService.CreateNewCalendarItemAsync(item, request.Attendees).ConfigureAwait(false);
|
||||||
|
else
|
||||||
|
await _calendarService.UpdateCalendarItemAsync(item, request.Attendees).ConfigureAwait(false);
|
||||||
|
|
||||||
|
await PersistIcsAsync(item, request.Attendees).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateCalendarEventAsync(UpdateCalendarEventRequest request)
|
||||||
|
{
|
||||||
|
var item = request.Item;
|
||||||
|
EnsureCalendarItemDefaults(item, _account, "local");
|
||||||
|
item.AssignedCalendar ??= await _calendarService.GetAccountCalendarAsync(item.CalendarId).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var attendees = request.Attendees ?? await _calendarService.GetAttendeesAsync(item.Id).ConfigureAwait(false);
|
||||||
|
|
||||||
|
await _calendarService.UpdateCalendarItemAsync(item, attendees).ConfigureAwait(false);
|
||||||
|
await PersistIcsAsync(item, attendees).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task DeleteCalendarEventAsync(DeleteCalendarEventRequest request)
|
||||||
|
=> _changeProcessor.DeleteCalendarItemAsync(request.Item.Id);
|
||||||
|
|
||||||
|
public async Task AcceptEventAsync(AcceptEventRequest request)
|
||||||
|
{
|
||||||
|
request.Item.Status = CalendarItemStatus.Accepted;
|
||||||
|
await UpdateStatusAsync(request.Item).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeclineEventAsync(DeclineEventRequest request)
|
||||||
|
{
|
||||||
|
request.Item.Status = CalendarItemStatus.Cancelled;
|
||||||
|
await UpdateStatusAsync(request.Item).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task TentativeEventAsync(TentativeEventRequest request)
|
||||||
|
{
|
||||||
|
request.Item.Status = CalendarItemStatus.Tentative;
|
||||||
|
await UpdateStatusAsync(request.Item).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateStatusAsync(CalendarItem item)
|
||||||
|
{
|
||||||
|
EnsureCalendarItemDefaults(item, _account, "local");
|
||||||
|
item.AssignedCalendar ??= await _calendarService.GetAccountCalendarAsync(item.CalendarId).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var attendees = await _calendarService.GetAttendeesAsync(item.Id).ConfigureAwait(false);
|
||||||
|
await _calendarService.UpdateCalendarItemAsync(item, attendees).ConfigureAwait(false);
|
||||||
|
await PersistIcsAsync(item, attendees).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task PersistIcsAsync(CalendarItem item, List<CalendarEventAttendee> attendees)
|
||||||
|
{
|
||||||
|
var resourceHref = $"{_resourceScheme}://calendar/{item.CalendarId:N}/{item.Id:N}";
|
||||||
|
var icsContent = BuildIcsContent(item, attendees);
|
||||||
|
|
||||||
|
return _changeProcessor.SaveCalendarItemIcsAsync(
|
||||||
|
_account.Id,
|
||||||
|
item.CalendarId,
|
||||||
|
item.Id,
|
||||||
|
item.RemoteEventId,
|
||||||
|
resourceHref,
|
||||||
|
DateTimeOffset.UtcNow.ToString("O"),
|
||||||
|
icsContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class CalDavCalendarOperationHandler : LocalCalendarOperationHandler
|
||||||
|
{
|
||||||
|
public CalDavCalendarOperationHandler(MailAccount account, IImapChangeProcessor changeProcessor, ICalendarService calendarService)
|
||||||
|
: base(account, changeProcessor, calendarService, "caldav")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureCalendarItemDefaults(CalendarItem item, MailAccount account, string idPrefix)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
throw new ArgumentNullException(nameof(item));
|
||||||
|
|
||||||
|
if (item.Id == Guid.Empty)
|
||||||
|
item.Id = Guid.NewGuid();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(item.RemoteEventId))
|
||||||
|
item.RemoteEventId = $"{idPrefix}-{item.Id:N}";
|
||||||
|
|
||||||
|
if (item.CreatedAt == default)
|
||||||
|
item.CreatedAt = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
|
item.UpdatedAt = DateTimeOffset.UtcNow;
|
||||||
|
item.OrganizerDisplayName ??= account?.SenderName ?? string.Empty;
|
||||||
|
item.OrganizerEmail ??= account?.Address ?? string.Empty;
|
||||||
|
item.StartTimeZone ??= TimeZoneInfo.Local.Id;
|
||||||
|
item.EndTimeZone ??= item.StartTimeZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildIcsContent(CalendarItem item, List<CalendarEventAttendee> attendees)
|
||||||
|
{
|
||||||
|
var uid = item.RemoteEventId?.Split(new[] { "::" }, StringSplitOptions.None)[0] ?? item.Id.ToString("N");
|
||||||
|
var dtStamp = DateTimeOffset.UtcNow.ToString("yyyyMMdd'T'HHmmss'Z'");
|
||||||
|
|
||||||
|
var lines = new List<string>
|
||||||
|
{
|
||||||
|
"BEGIN:VCALENDAR",
|
||||||
|
"VERSION:2.0",
|
||||||
|
"PRODID:-//Wino Mail//Calendar//EN",
|
||||||
|
"CALSCALE:GREGORIAN",
|
||||||
|
"BEGIN:VEVENT",
|
||||||
|
$"UID:{EscapeIcs(uid)}",
|
||||||
|
$"DTSTAMP:{dtStamp}",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (item.IsAllDayEvent)
|
||||||
|
{
|
||||||
|
lines.Add($"DTSTART;VALUE=DATE:{item.StartDate:yyyyMMdd}");
|
||||||
|
lines.Add($"DTEND;VALUE=DATE:{item.EndDate:yyyyMMdd}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lines.Add($"DTSTART:{item.StartDate.ToUniversalTime():yyyyMMdd'T'HHmmss'Z'}");
|
||||||
|
lines.Add($"DTEND:{item.EndDate.ToUniversalTime():yyyyMMdd'T'HHmmss'Z'}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(item.Title))
|
||||||
|
lines.Add($"SUMMARY:{EscapeIcs(item.Title)}");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(item.Description))
|
||||||
|
lines.Add($"DESCRIPTION:{EscapeIcs(item.Description)}");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(item.Location))
|
||||||
|
lines.Add($"LOCATION:{EscapeIcs(item.Location)}");
|
||||||
|
|
||||||
|
lines.Add($"STATUS:{MapStatus(item.Status)}");
|
||||||
|
lines.Add($"TRANSP:{(item.ShowAs == CalendarItemShowAs.Free ? "TRANSPARENT" : "OPAQUE")}");
|
||||||
|
lines.Add($"CLASS:{MapVisibility(item.Visibility)}");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(item.Recurrence))
|
||||||
|
{
|
||||||
|
var recurrenceLines = item.Recurrence
|
||||||
|
.Split(Wino.Core.Domain.Constants.CalendarEventRecurrenceRuleSeperator, StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(l => l.Trim())
|
||||||
|
.Where(l => !string.IsNullOrWhiteSpace(l));
|
||||||
|
|
||||||
|
lines.AddRange(recurrenceLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(item.OrganizerEmail))
|
||||||
|
{
|
||||||
|
var organizerName = string.IsNullOrWhiteSpace(item.OrganizerDisplayName)
|
||||||
|
? item.OrganizerEmail
|
||||||
|
: item.OrganizerDisplayName;
|
||||||
|
lines.Add($"ORGANIZER;CN={EscapeIcs(organizerName)}:mailto:{EscapeIcs(item.OrganizerEmail)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attendees != null)
|
||||||
|
{
|
||||||
|
foreach (var attendee in attendees.Where(a => !string.IsNullOrWhiteSpace(a.Email)))
|
||||||
|
{
|
||||||
|
var role = attendee.IsOptionalAttendee ? "OPT-PARTICIPANT" : "REQ-PARTICIPANT";
|
||||||
|
var partStat = attendee.AttendenceStatus switch
|
||||||
|
{
|
||||||
|
AttendeeStatus.Accepted => "ACCEPTED",
|
||||||
|
AttendeeStatus.Declined => "DECLINED",
|
||||||
|
AttendeeStatus.Tentative => "TENTATIVE",
|
||||||
|
_ => "NEEDS-ACTION"
|
||||||
|
};
|
||||||
|
|
||||||
|
var cn = string.IsNullOrWhiteSpace(attendee.Name) ? attendee.Email : attendee.Name;
|
||||||
|
lines.Add($"ATTENDEE;CN={EscapeIcs(cn)};ROLE={role};PARTSTAT={partStat}:mailto:{EscapeIcs(attendee.Email)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.Add("END:VEVENT");
|
||||||
|
lines.Add("END:VCALENDAR");
|
||||||
|
|
||||||
|
return string.Join(Environment.NewLine, lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string EscapeIcs(string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return value
|
||||||
|
.Replace("\\", "\\\\", StringComparison.Ordinal)
|
||||||
|
.Replace(";", "\\;", StringComparison.Ordinal)
|
||||||
|
.Replace(",", "\\,", StringComparison.Ordinal)
|
||||||
|
.Replace("\r\n", "\\n", StringComparison.Ordinal)
|
||||||
|
.Replace("\n", "\\n", StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string MapStatus(CalendarItemStatus status)
|
||||||
|
{
|
||||||
|
return status switch
|
||||||
|
{
|
||||||
|
CalendarItemStatus.Cancelled => "CANCELLED",
|
||||||
|
CalendarItemStatus.Tentative => "TENTATIVE",
|
||||||
|
_ => "CONFIRMED"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string MapVisibility(CalendarItemVisibility visibility)
|
||||||
|
{
|
||||||
|
return visibility switch
|
||||||
|
{
|
||||||
|
CalendarItemVisibility.Public => "PUBLIC",
|
||||||
|
CalendarItemVisibility.Private => "PRIVATE",
|
||||||
|
CalendarItemVisibility.Confidential => "CONFIDENTIAL",
|
||||||
|
_ => "PUBLIC"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public Task StartIdleClientAsync()
|
public Task StartIdleClientAsync()
|
||||||
{
|
{
|
||||||
if (IsDisposing)
|
if (IsDisposing)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ using Wino.Core.Domain.Models.Synchronization;
|
|||||||
using Wino.Core.Extensions;
|
using Wino.Core.Extensions;
|
||||||
using Wino.Core.Http;
|
using Wino.Core.Http;
|
||||||
using Wino.Core.Integration.Processors;
|
using Wino.Core.Integration.Processors;
|
||||||
|
using Wino.Core.Misc;
|
||||||
using Wino.Core.Requests.Bundles;
|
using Wino.Core.Requests.Bundles;
|
||||||
using Wino.Core.Requests.Calendar;
|
using Wino.Core.Requests.Calendar;
|
||||||
using Wino.Core.Requests.Folder;
|
using Wino.Core.Requests.Folder;
|
||||||
@@ -2109,6 +2110,11 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
|
|||||||
var remotePrimaryCalendarId = await GetPrimaryCalendarIdAsync(calendars.Value, cancellationToken).ConfigureAwait(false);
|
var remotePrimaryCalendarId = await GetPrimaryCalendarIdAsync(calendars.Value, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var localCalendars = await _outlookChangeProcessor.GetAccountCalendarsAsync(Account.Id).ConfigureAwait(false);
|
var localCalendars = await _outlookChangeProcessor.GetAccountCalendarsAsync(Account.Id).ConfigureAwait(false);
|
||||||
|
var usedCalendarColors = new HashSet<string>(
|
||||||
|
localCalendars
|
||||||
|
.Select(a => a.BackgroundColorHex)
|
||||||
|
.Where(a => !string.IsNullOrWhiteSpace(a)),
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
List<AccountCalendar> insertedCalendars = new();
|
List<AccountCalendar> insertedCalendars = new();
|
||||||
List<AccountCalendar> updatedCalendars = new();
|
List<AccountCalendar> updatedCalendars = new();
|
||||||
@@ -2138,8 +2144,12 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
|
|||||||
if (existingLocalCalendar == null)
|
if (existingLocalCalendar == null)
|
||||||
{
|
{
|
||||||
// Insert new calendar.
|
// Insert new calendar.
|
||||||
var localCalendar = calendar.AsCalendar(Account);
|
var fallbackColor = ColorHelpers.GetDistinctFlatColorHex(usedCalendarColors);
|
||||||
|
var localCalendar = calendar.AsCalendar(Account, fallbackColor);
|
||||||
localCalendar.IsPrimary = string.Equals(localCalendar.RemoteCalendarId, remotePrimaryCalendarId, StringComparison.OrdinalIgnoreCase);
|
localCalendar.IsPrimary = string.Equals(localCalendar.RemoteCalendarId, remotePrimaryCalendarId, StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (string.IsNullOrWhiteSpace(localCalendar.BackgroundColorHex) || usedCalendarColors.Contains(localCalendar.BackgroundColorHex))
|
||||||
|
localCalendar.BackgroundColorHex = ColorHelpers.GetDistinctFlatColorHex(usedCalendarColors);
|
||||||
|
usedCalendarColors.Add(localCalendar.BackgroundColorHex);
|
||||||
insertedCalendars.Add(localCalendar);
|
insertedCalendars.Add(localCalendar);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using Wino.Core.Domain.Entities.Shared;
|
|||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Exceptions;
|
using Wino.Core.Domain.Exceptions;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
using Wino.Core.Domain.Models.Navigation;
|
using Wino.Core.Domain.Models.Navigation;
|
||||||
using Wino.Core.Services;
|
using Wino.Core.Services;
|
||||||
using Wino.Core.ViewModels;
|
using Wino.Core.ViewModels;
|
||||||
@@ -25,6 +26,8 @@ namespace Wino.Mail.ViewModels;
|
|||||||
public partial class AccountManagementViewModel : AccountManagementPageViewModelBase
|
public partial class AccountManagementViewModel : AccountManagementPageViewModelBase
|
||||||
{
|
{
|
||||||
private readonly IWinoLogger _winoLogger;
|
private readonly IWinoLogger _winoLogger;
|
||||||
|
private readonly ISpecialImapProviderConfigResolver _specialImapProviderConfigResolver;
|
||||||
|
private readonly ICalDavClient _calDavClient;
|
||||||
|
|
||||||
public IMailDialogService MailDialogService { get; }
|
public IMailDialogService MailDialogService { get; }
|
||||||
|
|
||||||
@@ -34,11 +37,15 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
IProviderService providerService,
|
IProviderService providerService,
|
||||||
IStoreManagementService storeManagementService,
|
IStoreManagementService storeManagementService,
|
||||||
IWinoLogger winoLogger,
|
IWinoLogger winoLogger,
|
||||||
|
ISpecialImapProviderConfigResolver specialImapProviderConfigResolver,
|
||||||
|
ICalDavClient calDavClient,
|
||||||
IAuthenticationProvider authenticationProvider,
|
IAuthenticationProvider authenticationProvider,
|
||||||
IPreferencesService preferencesService) : base(dialogService, navigationService, accountService, providerService, storeManagementService, authenticationProvider, preferencesService)
|
IPreferencesService preferencesService) : base(dialogService, navigationService, accountService, providerService, storeManagementService, authenticationProvider, preferencesService)
|
||||||
{
|
{
|
||||||
MailDialogService = dialogService;
|
MailDialogService = dialogService;
|
||||||
_winoLogger = winoLogger;
|
_winoLogger = winoLogger;
|
||||||
|
_specialImapProviderConfigResolver = specialImapProviderConfigResolver;
|
||||||
|
_calDavClient = calDavClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
@@ -86,7 +93,7 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
var providers = ProviderService.GetAvailableProviders();
|
var providers = ProviderService.GetAvailableProviders();
|
||||||
|
|
||||||
// Select provider.
|
// Select provider.
|
||||||
var accountCreationDialogResult = await MailDialogService.ShowAccountProviderSelectionDialogAsync(providers);
|
var accountCreationDialogResult = await ExecuteUIThreadTaskAsync(() => MailDialogService.ShowAccountProviderSelectionDialogAsync(providers));
|
||||||
|
|
||||||
if (accountCreationDialogResult != null)
|
if (accountCreationDialogResult != null)
|
||||||
{
|
{
|
||||||
@@ -104,35 +111,61 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
|
|
||||||
if (accountCreationDialogResult.ProviderType == MailProviderType.IMAP4)
|
if (accountCreationDialogResult.ProviderType == MailProviderType.IMAP4)
|
||||||
{
|
{
|
||||||
var completionSource = new TaskCompletionSource<ImapCalDavSetupResult>();
|
if (createdAccount.SpecialImapProvider == SpecialImapProvider.iCloud || createdAccount.SpecialImapProvider == SpecialImapProvider.Yahoo)
|
||||||
var setupContext = ImapCalDavSettingsNavigationContext.CreateForCreateMode(accountCreationDialogResult, completionSource);
|
{
|
||||||
|
var accountCreationCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
creationDialog = MailDialogService.GetAccountCreationDialog(accountCreationDialogResult);
|
||||||
|
|
||||||
Messenger.Send(new BreadcrumbNavigationRequested(
|
await ExecuteUIThreadTaskAsync(() => creationDialog.ShowDialogAsync(accountCreationCancellationTokenSource));
|
||||||
Translator.ImapCalDavSettingsPage_TitleCreate,
|
await Task.Delay(500);
|
||||||
WinoPage.ImapCalDavSettingsPage,
|
|
||||||
setupContext));
|
|
||||||
|
|
||||||
var setupResult = await completionSource.Task.ConfigureAwait(false)
|
await ExecuteUIThread(() => creationDialog.State = AccountCreationDialogState.SigningIn);
|
||||||
?? throw new AccountSetupCanceledException();
|
|
||||||
|
|
||||||
customServerInformation = setupResult.ServerInformation ?? throw new AccountSetupCanceledException();
|
customServerInformation = _specialImapProviderConfigResolver.GetServerInformation(createdAccount, accountCreationDialogResult)
|
||||||
customServerInformation.Id = Guid.NewGuid();
|
?? throw new AccountSetupCanceledException();
|
||||||
customServerInformation.AccountId = createdAccount.Id;
|
|
||||||
|
|
||||||
createdAccount.Address = setupResult.EmailAddress;
|
customServerInformation.Id = Guid.NewGuid();
|
||||||
createdAccount.SenderName = setupResult.DisplayName;
|
customServerInformation.AccountId = createdAccount.Id;
|
||||||
createdAccount.IsCalendarAccessGranted = setupResult.IsCalendarAccessGranted;
|
|
||||||
createdAccount.ServerInformation = customServerInformation;
|
createdAccount.Address = accountCreationDialogResult.SpecialImapProviderDetails.Address;
|
||||||
|
createdAccount.SenderName = accountCreationDialogResult.SpecialImapProviderDetails.SenderName;
|
||||||
|
createdAccount.IsCalendarAccessGranted = customServerInformation.CalendarSupportMode == ImapCalendarSupportMode.CalDav;
|
||||||
|
createdAccount.ServerInformation = customServerInformation;
|
||||||
|
|
||||||
|
await ValidateSpecialImapConnectivityAsync(customServerInformation).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var completionSource = new TaskCompletionSource<ImapCalDavSetupResult>();
|
||||||
|
var setupContext = ImapCalDavSettingsNavigationContext.CreateForCreateMode(accountCreationDialogResult, completionSource);
|
||||||
|
|
||||||
|
await ExecuteUIThread(() => Messenger.Send(new BreadcrumbNavigationRequested(
|
||||||
|
Translator.ImapCalDavSettingsPage_TitleCreate,
|
||||||
|
WinoPage.ImapCalDavSettingsPage,
|
||||||
|
setupContext)));
|
||||||
|
|
||||||
|
var setupResult = await completionSource.Task.ConfigureAwait(false)
|
||||||
|
?? throw new AccountSetupCanceledException();
|
||||||
|
|
||||||
|
customServerInformation = setupResult.ServerInformation ?? throw new AccountSetupCanceledException();
|
||||||
|
customServerInformation.Id = Guid.NewGuid();
|
||||||
|
customServerInformation.AccountId = createdAccount.Id;
|
||||||
|
|
||||||
|
createdAccount.Address = setupResult.EmailAddress;
|
||||||
|
createdAccount.SenderName = setupResult.DisplayName;
|
||||||
|
createdAccount.IsCalendarAccessGranted = setupResult.IsCalendarAccessGranted;
|
||||||
|
createdAccount.ServerInformation = customServerInformation;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var accountCreationCancellationTokenSource = new CancellationTokenSource();
|
var accountCreationCancellationTokenSource = new CancellationTokenSource();
|
||||||
creationDialog = MailDialogService.GetAccountCreationDialog(accountCreationDialogResult);
|
creationDialog = MailDialogService.GetAccountCreationDialog(accountCreationDialogResult);
|
||||||
|
|
||||||
await creationDialog.ShowDialogAsync(accountCreationCancellationTokenSource);
|
await ExecuteUIThreadTaskAsync(() => creationDialog.ShowDialogAsync(accountCreationCancellationTokenSource));
|
||||||
await Task.Delay(500);
|
await Task.Delay(500);
|
||||||
|
|
||||||
creationDialog.State = AccountCreationDialogState.SigningIn;
|
await ExecuteUIThread(() => creationDialog.State = AccountCreationDialogState.SigningIn);
|
||||||
|
|
||||||
// OAuth authentication is handled here.
|
// OAuth authentication is handled here.
|
||||||
// Use SynchronizationManager to handle OAuth authentication.
|
// Use SynchronizationManager to handle OAuth authentication.
|
||||||
@@ -142,7 +175,10 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
createdAccount,
|
createdAccount,
|
||||||
createdAccount.ProviderType == MailProviderType.Gmail);
|
createdAccount.ProviderType == MailProviderType.Gmail);
|
||||||
|
|
||||||
if (creationDialog.State == AccountCreationDialogState.Canceled)
|
bool creationCanceled = false;
|
||||||
|
await ExecuteUIThread(() => creationCanceled = creationDialog.State == AccountCreationDialogState.Canceled);
|
||||||
|
|
||||||
|
if (creationCanceled)
|
||||||
throw new AccountSetupCanceledException();
|
throw new AccountSetupCanceledException();
|
||||||
|
|
||||||
// Update account address with authenticated user information
|
// Update account address with authenticated user information
|
||||||
@@ -182,7 +218,7 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (creationDialog != null)
|
if (creationDialog != null)
|
||||||
creationDialog.State = AccountCreationDialogState.PreparingFolders;
|
await ExecuteUIThread(() => creationDialog.State = AccountCreationDialogState.PreparingFolders);
|
||||||
|
|
||||||
var folderSynchronizationResult = await SynchronizationManager.Instance.SynchronizeFoldersAsync(createdAccount.Id);
|
var folderSynchronizationResult = await SynchronizationManager.Instance.SynchronizeFoldersAsync(createdAccount.Id);
|
||||||
|
|
||||||
@@ -207,10 +243,10 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send changes to listeners.
|
// Send changes to listeners.
|
||||||
ReportUIChange(new AccountCreatedMessage(createdAccount));
|
await ExecuteUIThread(() => ReportUIChange(new AccountCreatedMessage(createdAccount)));
|
||||||
|
|
||||||
// Notify success.
|
// Notify success.
|
||||||
DialogService.InfoBarMessage(Translator.Info_AccountCreatedTitle, string.Format(Translator.Info_AccountCreatedMessage, createdAccount.Address), InfoBarMessageType.Success);
|
await ExecuteUIThread(() => DialogService.InfoBarMessage(Translator.Info_AccountCreatedTitle, string.Format(Translator.Info_AccountCreatedMessage, createdAccount.Address), InfoBarMessageType.Success));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (ex.Message.Contains(nameof(GmailServiceDisabledException)))
|
catch (Exception ex) when (ex.Message.Contains(nameof(GmailServiceDisabledException)))
|
||||||
@@ -219,7 +255,7 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
// Wino can't continue synchronization in this case.
|
// Wino can't continue synchronization in this case.
|
||||||
// We must notify the user about this and prevent account creation.
|
// We must notify the user about this and prevent account creation.
|
||||||
|
|
||||||
DialogService.InfoBarMessage(Translator.GmailServiceDisabled_Title, Translator.GmailServiceDisabled_Message, InfoBarMessageType.Error);
|
await ExecuteUIThread(() => DialogService.InfoBarMessage(Translator.GmailServiceDisabled_Title, Translator.GmailServiceDisabled_Message, InfoBarMessageType.Error));
|
||||||
|
|
||||||
if (createdAccount != null)
|
if (createdAccount != null)
|
||||||
{
|
{
|
||||||
@@ -243,17 +279,17 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
|
|
||||||
_winoLogger.TrackEvent("IMAP Test Failed", properties);
|
_winoLogger.TrackEvent("IMAP Test Failed", properties);
|
||||||
|
|
||||||
DialogService.InfoBarMessage(Translator.Info_AccountCreationFailedTitle, testClientPoolException.Message, InfoBarMessageType.Error);
|
await ExecuteUIThread(() => DialogService.InfoBarMessage(Translator.Info_AccountCreationFailedTitle, testClientPoolException.Message, InfoBarMessageType.Error));
|
||||||
}
|
}
|
||||||
catch (ImapClientPoolException clientPoolException) when (clientPoolException.InnerException != null)
|
catch (ImapClientPoolException clientPoolException) when (clientPoolException.InnerException != null)
|
||||||
{
|
{
|
||||||
DialogService.InfoBarMessage(Translator.Info_AccountCreationFailedTitle, clientPoolException.InnerException.Message, InfoBarMessageType.Error);
|
await ExecuteUIThread(() => DialogService.InfoBarMessage(Translator.Info_AccountCreationFailedTitle, clientPoolException.InnerException.Message, InfoBarMessageType.Error));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Error(ex, "Failed to create account.");
|
Log.Error(ex, "Failed to create account.");
|
||||||
|
|
||||||
DialogService.InfoBarMessage(Translator.Info_AccountCreationFailedTitle, ex.Message, InfoBarMessageType.Error);
|
await ExecuteUIThread(() => DialogService.InfoBarMessage(Translator.Info_AccountCreationFailedTitle, ex.Message, InfoBarMessageType.Error));
|
||||||
|
|
||||||
// Delete account in case of failure.
|
// Delete account in case of failure.
|
||||||
if (createdAccount != null)
|
if (createdAccount != null)
|
||||||
@@ -263,10 +299,115 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
creationDialog?.Complete(false);
|
await ExecuteUIThread(() => { creationDialog?.Complete(false); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void EditMergedAccounts(MergedAccountProviderDetailViewModel mergedAccountProviderDetailViewModel)
|
private void EditMergedAccounts(MergedAccountProviderDetailViewModel mergedAccountProviderDetailViewModel)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -105,6 +105,7 @@
|
|||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
@@ -142,8 +143,14 @@
|
|||||||
Header="App-Specific Password"
|
Header="App-Specific Password"
|
||||||
PasswordChanged="ImapPasswordChanged" />
|
PasswordChanged="ImapPasswordChanged" />
|
||||||
|
|
||||||
<HyperlinkButton
|
<ComboBox
|
||||||
Grid.Row="4"
|
Grid.Row="4"
|
||||||
|
Header="{x:Bind domain:Translator.ImapCalDavSettingsPage_CalendarModeHeader, Mode=OneWay}"
|
||||||
|
ItemsSource="{x:Bind CalendarModeOptions, Mode=OneWay}"
|
||||||
|
SelectedIndex="{x:Bind SelectedCalendarModeIndex, Mode=TwoWay}" />
|
||||||
|
|
||||||
|
<HyperlinkButton
|
||||||
|
Grid.Row="5"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
Click="AppSpecificHelpButtonClicked"
|
Click="AppSpecificHelpButtonClicked"
|
||||||
Content="How do I get app-specific password?" />
|
Content="How do I get app-specific password?" />
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Linq;
|
|||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using Windows.System;
|
using Windows.System;
|
||||||
|
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;
|
||||||
@@ -24,6 +25,7 @@ public sealed partial class NewAccountDialog : ContentDialog
|
|||||||
public static readonly DependencyProperty IsSpecialImapServerPartVisibleProperty = DependencyProperty.Register(nameof(IsSpecialImapServerPartVisible), typeof(bool), typeof(NewAccountDialog), new PropertyMetadata(false));
|
public static readonly DependencyProperty IsSpecialImapServerPartVisibleProperty = DependencyProperty.Register(nameof(IsSpecialImapServerPartVisible), typeof(bool), typeof(NewAccountDialog), new PropertyMetadata(false));
|
||||||
public static readonly DependencyProperty SelectedMailProviderProperty = DependencyProperty.Register(nameof(SelectedMailProvider), typeof(ProviderDetail), typeof(NewAccountDialog), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedProviderChanged)));
|
public static readonly DependencyProperty SelectedMailProviderProperty = DependencyProperty.Register(nameof(SelectedMailProvider), typeof(ProviderDetail), typeof(NewAccountDialog), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedProviderChanged)));
|
||||||
public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register(nameof(SelectedColor), typeof(AppColorViewModel), typeof(NewAccountDialog), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedColorChanged)));
|
public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register(nameof(SelectedColor), typeof(AppColorViewModel), typeof(NewAccountDialog), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedColorChanged)));
|
||||||
|
public static readonly DependencyProperty SelectedCalendarModeIndexProperty = DependencyProperty.Register(nameof(SelectedCalendarModeIndex), typeof(int), typeof(NewAccountDialog), new PropertyMetadata(0));
|
||||||
|
|
||||||
|
|
||||||
public AppColorViewModel SelectedColor
|
public AppColorViewModel SelectedColor
|
||||||
@@ -32,6 +34,12 @@ public sealed partial class NewAccountDialog : ContentDialog
|
|||||||
set { SetValue(SelectedColorProperty, value); }
|
set { SetValue(SelectedColorProperty, value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int SelectedCalendarModeIndex
|
||||||
|
{
|
||||||
|
get { return (int)GetValue(SelectedCalendarModeIndexProperty); }
|
||||||
|
set { SetValue(SelectedCalendarModeIndexProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets current selected mail provider in the dialog.
|
/// Gets or sets current selected mail provider in the dialog.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -59,6 +67,12 @@ public sealed partial class NewAccountDialog : ContentDialog
|
|||||||
public List<IProviderDetail> Providers { get; set; }
|
public List<IProviderDetail> Providers { get; set; }
|
||||||
|
|
||||||
public List<AppColorViewModel> AvailableColors { get; set; }
|
public List<AppColorViewModel> AvailableColors { get; set; }
|
||||||
|
public List<string> CalendarModeOptions { get; } =
|
||||||
|
[
|
||||||
|
Translator.ImapCalDavSettingsPage_CalendarModeCalDav,
|
||||||
|
Translator.ImapCalDavSettingsPage_CalendarModeLocalOnly,
|
||||||
|
Translator.ImapCalDavSettingsPage_CalendarModeDisabled
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
public AccountCreationDialogResult Result = null;
|
public AccountCreationDialogResult Result = null;
|
||||||
@@ -102,8 +116,19 @@ public sealed partial class NewAccountDialog : ContentDialog
|
|||||||
if (IsSpecialImapServerPartVisible)
|
if (IsSpecialImapServerPartVisible)
|
||||||
{
|
{
|
||||||
// Special imap detail input.
|
// Special imap detail input.
|
||||||
|
var calendarSupportMode = SelectedCalendarModeIndex switch
|
||||||
|
{
|
||||||
|
1 => ImapCalendarSupportMode.LocalOnly,
|
||||||
|
2 => ImapCalendarSupportMode.Disabled,
|
||||||
|
_ => ImapCalendarSupportMode.CalDav
|
||||||
|
};
|
||||||
|
|
||||||
var details = new SpecialImapProviderDetails(SpecialImapAddress.Text.Trim(), AppSpecificPassword.Password.Trim(), DisplayNameTextBox.Text.Trim(), SelectedMailProvider.SpecialImapProvider);
|
var details = new SpecialImapProviderDetails(
|
||||||
|
SpecialImapAddress.Text.Trim(),
|
||||||
|
AppSpecificPassword.Password.Trim(),
|
||||||
|
DisplayNameTextBox.Text.Trim(),
|
||||||
|
SelectedMailProvider.SpecialImapProvider,
|
||||||
|
calendarSupportMode);
|
||||||
Result = new AccountCreationDialogResult(SelectedMailProvider.Type, AccountNameTextbox.Text.Trim(), details, SelectedColor?.Hex ?? string.Empty);
|
Result = new AccountCreationDialogResult(SelectedMailProvider.Type, AccountNameTextbox.Text.Trim(), details, SelectedColor?.Hex ?? string.Empty);
|
||||||
Hide();
|
Hide();
|
||||||
|
|
||||||
|
|||||||
@@ -3,20 +3,29 @@
|
|||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:abstract="using:Wino.Views.Abstract"
|
xmlns:abstract="using:Wino.Views.Abstract"
|
||||||
xmlns:helpers="using:Wino.Helpers"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:helpers="using:Wino.Helpers"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
<StackPanel MaxWidth="1040" Padding="24,20,24,24" Spacing="16">
|
<StackPanel
|
||||||
|
MaxWidth="1040"
|
||||||
|
Padding="24,20,24,24"
|
||||||
|
Spacing="16">
|
||||||
<StackPanel Spacing="4">
|
<StackPanel Spacing="4">
|
||||||
<TextBlock FontSize="30" FontWeight="SemiBold" Text="{x:Bind ViewModel.PageTitle, Mode=OneWay}" />
|
<TextBlock
|
||||||
|
FontSize="30"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Text="{x:Bind ViewModel.PageTitle, Mode=OneWay}" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Opacity="0.85"
|
Opacity="0.85"
|
||||||
Text="{x:Bind ViewModel.SubtitleText, Mode=OneWay}"
|
Text="{x:Bind ViewModel.SubtitleText, Mode=OneWay}"
|
||||||
TextWrapping="WrapWholeWords" />
|
TextWrapping="WrapWholeWords" />
|
||||||
<TextBlock Opacity="0.85" Text="{x:Bind ViewModel.ProviderHint, Mode=OneWay}" TextWrapping="WrapWholeWords" />
|
<TextBlock
|
||||||
|
Opacity="0.85"
|
||||||
|
Text="{x:Bind ViewModel.ProviderHint, Mode=OneWay}"
|
||||||
|
TextWrapping="WrapWholeWords" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<SelectorBar x:Name="SetupModeSelector" SelectionChanged="OnSetupModeSelectionChanged">
|
<SelectorBar x:Name="SetupModeSelector" SelectionChanged="OnSetupModeSelectionChanged">
|
||||||
@@ -32,15 +41,29 @@
|
|||||||
CornerRadius="12"
|
CornerRadius="12"
|
||||||
Visibility="{x:Bind helpers:XamlHelpers.ReverseBoolToVisibilityConverter(ViewModel.IsAdvancedSetupSelected), Mode=OneWay}">
|
Visibility="{x:Bind helpers:XamlHelpers.ReverseBoolToVisibilityConverter(ViewModel.IsAdvancedSetupSelected), Mode=OneWay}">
|
||||||
<StackPanel Spacing="12">
|
<StackPanel Spacing="12">
|
||||||
<TextBlock FontSize="19" FontWeight="SemiBold" Text="{x:Bind ViewModel.BasicSectionTitleText, Mode=OneWay}" />
|
<TextBlock
|
||||||
<TextBlock Opacity="0.75" Text="{x:Bind ViewModel.BasicSectionDescriptionText, Mode=OneWay}" TextWrapping="WrapWholeWords" />
|
FontSize="19"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Text="{x:Bind ViewModel.BasicSectionTitleText, Mode=OneWay}" />
|
||||||
|
<TextBlock
|
||||||
|
Opacity="0.75"
|
||||||
|
Text="{x:Bind ViewModel.BasicSectionDescriptionText, Mode=OneWay}"
|
||||||
|
TextWrapping="WrapWholeWords" />
|
||||||
<Grid ColumnSpacing="12">
|
<Grid ColumnSpacing="12">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<TextBox Grid.Column="0" Header="{x:Bind ViewModel.DisplayNameHeaderText, Mode=OneWay}" PlaceholderText="{x:Bind ViewModel.DisplayNamePlaceholderText, Mode=OneWay}" Text="{x:Bind ViewModel.DisplayName, Mode=TwoWay}" />
|
<TextBox
|
||||||
<TextBox Grid.Column="1" Header="{x:Bind ViewModel.EmailAddressHeaderText, Mode=OneWay}" PlaceholderText="{x:Bind ViewModel.EmailAddressPlaceholderText, Mode=OneWay}" Text="{x:Bind ViewModel.EmailAddress, Mode=TwoWay}" />
|
Grid.Column="0"
|
||||||
|
Header="{x:Bind ViewModel.DisplayNameHeaderText, Mode=OneWay}"
|
||||||
|
PlaceholderText="{x:Bind ViewModel.DisplayNamePlaceholderText, Mode=OneWay}"
|
||||||
|
Text="{x:Bind ViewModel.DisplayName, Mode=TwoWay}" />
|
||||||
|
<TextBox
|
||||||
|
Grid.Column="1"
|
||||||
|
Header="{x:Bind ViewModel.EmailAddressHeaderText, Mode=OneWay}"
|
||||||
|
PlaceholderText="{x:Bind ViewModel.EmailAddressPlaceholderText, Mode=OneWay}"
|
||||||
|
Text="{x:Bind ViewModel.EmailAddress, Mode=TwoWay}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<PasswordBox Header="{x:Bind ViewModel.PasswordHeaderText, Mode=OneWay}" Password="{x:Bind ViewModel.Password, Mode=TwoWay}" />
|
<PasswordBox Header="{x:Bind ViewModel.PasswordHeaderText, Mode=OneWay}" Password="{x:Bind ViewModel.Password, Mode=TwoWay}" />
|
||||||
<CheckBox Content="{x:Bind ViewModel.EnableCalendarSupportText, Mode=OneWay}" IsChecked="{x:Bind ViewModel.IsCalendarSupportEnabled, Mode=TwoWay}" />
|
<CheckBox Content="{x:Bind ViewModel.EnableCalendarSupportText, Mode=OneWay}" IsChecked="{x:Bind ViewModel.IsCalendarSupportEnabled, Mode=TwoWay}" />
|
||||||
@@ -59,7 +82,10 @@
|
|||||||
CornerRadius="12"
|
CornerRadius="12"
|
||||||
Visibility="{x:Bind helpers:XamlHelpers.ReverseBoolToVisibilityConverter(ViewModel.IsBasicSetupSelected), Mode=OneWay}">
|
Visibility="{x:Bind helpers:XamlHelpers.ReverseBoolToVisibilityConverter(ViewModel.IsBasicSetupSelected), Mode=OneWay}">
|
||||||
<StackPanel Spacing="14">
|
<StackPanel Spacing="14">
|
||||||
<TextBlock FontSize="19" FontWeight="SemiBold" Text="{x:Bind ViewModel.AdvancedSectionTitleText, Mode=OneWay}" />
|
<TextBlock
|
||||||
|
FontSize="19"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Text="{x:Bind ViewModel.AdvancedSectionTitleText, Mode=OneWay}" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Opacity="0.75"
|
Opacity="0.75"
|
||||||
Text="{x:Bind ViewModel.AdvancedSectionDescriptionText, Mode=OneWay}"
|
Text="{x:Bind ViewModel.AdvancedSectionDescriptionText, Mode=OneWay}"
|
||||||
@@ -113,14 +139,23 @@
|
|||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
CornerRadius="12">
|
CornerRadius="12">
|
||||||
<StackPanel Spacing="12">
|
<StackPanel Spacing="12">
|
||||||
<TextBlock FontSize="19" FontWeight="SemiBold" Text="{x:Bind ViewModel.CalendarSectionTitleText, Mode=OneWay}" />
|
<TextBlock
|
||||||
<TextBlock Opacity="0.75" Text="{x:Bind ViewModel.CalendarSectionDescriptionText, Mode=OneWay}" TextWrapping="WrapWholeWords" />
|
FontSize="19"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Text="{x:Bind ViewModel.CalendarSectionTitleText, Mode=OneWay}" />
|
||||||
|
<TextBlock
|
||||||
|
Opacity="0.75"
|
||||||
|
Text="{x:Bind ViewModel.CalendarSectionDescriptionText, Mode=OneWay}"
|
||||||
|
TextWrapping="WrapWholeWords" />
|
||||||
<ComboBox
|
<ComboBox
|
||||||
Header="{x:Bind ViewModel.CalendarModeHeaderText, Mode=OneWay}"
|
Header="{x:Bind ViewModel.CalendarModeHeaderText, Mode=OneWay}"
|
||||||
IsEnabled="{x:Bind ViewModel.IsCalendarModeSelectionVisible, Mode=OneWay}"
|
IsEnabled="{x:Bind ViewModel.IsCalendarModeSelectionVisible, Mode=OneWay}"
|
||||||
ItemsSource="{x:Bind ViewModel.AvailableCalendarSupportModeTitles}"
|
ItemsSource="{x:Bind ViewModel.AvailableCalendarSupportModeTitles}"
|
||||||
SelectedIndex="{x:Bind ViewModel.SelectedCalendarSupportModeIndex, Mode=TwoWay}" />
|
SelectedIndex="{x:Bind ViewModel.SelectedCalendarSupportModeIndex, Mode=TwoWay}" />
|
||||||
<TextBlock Opacity="0.8" Text="{x:Bind ViewModel.SelectedCalendarSupportDescription, Mode=OneWay}" TextWrapping="WrapWholeWords" />
|
<TextBlock
|
||||||
|
Opacity="0.8"
|
||||||
|
Text="{x:Bind ViewModel.SelectedCalendarSupportDescription, Mode=OneWay}"
|
||||||
|
TextWrapping="WrapWholeWords" />
|
||||||
<Button
|
<Button
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
Command="{x:Bind ViewModel.ShowLocalCalendarExplanationCommand}"
|
Command="{x:Bind ViewModel.ShowLocalCalendarExplanationCommand}"
|
||||||
@@ -157,14 +192,24 @@
|
|||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Button Grid.Column="0" Command="{x:Bind ViewModel.TestImapConnectionCommand}" Content="{x:Bind ViewModel.TestImapButtonText, Mode=OneWay}" />
|
<Button
|
||||||
|
Grid.Column="0"
|
||||||
|
Command="{x:Bind ViewModel.TestImapConnectionCommand}"
|
||||||
|
Content="{x:Bind ViewModel.TestImapButtonText, Mode=OneWay}" />
|
||||||
<Button
|
<Button
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Command="{x:Bind ViewModel.TestCalDavConnectionCommand}"
|
Command="{x:Bind ViewModel.TestCalDavConnectionCommand}"
|
||||||
Content="{x:Bind ViewModel.TestCalDavButtonText, Mode=OneWay}"
|
Content="{x:Bind ViewModel.TestCalDavButtonText, Mode=OneWay}"
|
||||||
IsEnabled="{x:Bind ViewModel.IsCalDavSettingsVisible, Mode=OneWay}" />
|
IsEnabled="{x:Bind ViewModel.IsCalDavSettingsVisible, Mode=OneWay}" />
|
||||||
<Button Grid.Column="3" Command="{x:Bind ViewModel.CancelCommand}" Content="{x:Bind ViewModel.CancelButtonText, Mode=OneWay}" />
|
<Button
|
||||||
<Button Grid.Column="4" Command="{x:Bind ViewModel.SaveCommand}" Content="{x:Bind ViewModel.SaveButtonText, Mode=OneWay}" Style="{ThemeResource AccentButtonStyle}" />
|
Grid.Column="3"
|
||||||
|
Command="{x:Bind ViewModel.CancelCommand}"
|
||||||
|
Content="{x:Bind ViewModel.CancelButtonText, Mode=OneWay}" />
|
||||||
|
<Button
|
||||||
|
Grid.Column="4"
|
||||||
|
Command="{x:Bind ViewModel.SaveCommand}"
|
||||||
|
Content="{x:Bind ViewModel.SaveButtonText, Mode=OneWay}"
|
||||||
|
Style="{ThemeResource AccentButtonStyle}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|||||||
@@ -7,61 +7,70 @@ namespace Wino.Services;
|
|||||||
|
|
||||||
public class SpecialImapProviderConfigResolver : ISpecialImapProviderConfigResolver
|
public class SpecialImapProviderConfigResolver : ISpecialImapProviderConfigResolver
|
||||||
{
|
{
|
||||||
private readonly CustomServerInformation iCloudServerConfig = new CustomServerInformation()
|
|
||||||
{
|
|
||||||
IncomingServer = "imap.mail.me.com",
|
|
||||||
IncomingServerPort = "993",
|
|
||||||
IncomingServerType = CustomIncomingServerType.IMAP4,
|
|
||||||
IncomingServerSocketOption = ImapConnectionSecurity.Auto,
|
|
||||||
IncomingAuthenticationMethod = ImapAuthenticationMethod.Auto,
|
|
||||||
OutgoingServer = "smtp.mail.me.com",
|
|
||||||
OutgoingServerPort = "587",
|
|
||||||
OutgoingServerSocketOption = ImapConnectionSecurity.Auto,
|
|
||||||
OutgoingAuthenticationMethod = ImapAuthenticationMethod.Auto,
|
|
||||||
MaxConcurrentClients = 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly CustomServerInformation yahooServerConfig = new CustomServerInformation()
|
|
||||||
{
|
|
||||||
IncomingServer = "imap.mail.yahoo.com",
|
|
||||||
IncomingServerPort = "993",
|
|
||||||
IncomingServerType = CustomIncomingServerType.IMAP4,
|
|
||||||
IncomingServerSocketOption = ImapConnectionSecurity.Auto,
|
|
||||||
IncomingAuthenticationMethod = ImapAuthenticationMethod.Auto,
|
|
||||||
OutgoingServer = "smtp.mail.yahoo.com",
|
|
||||||
OutgoingServerPort = "587",
|
|
||||||
OutgoingServerSocketOption = ImapConnectionSecurity.Auto,
|
|
||||||
OutgoingAuthenticationMethod = ImapAuthenticationMethod.Auto,
|
|
||||||
MaxConcurrentClients = 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
public CustomServerInformation GetServerInformation(MailAccount account, AccountCreationDialogResult dialogResult)
|
public CustomServerInformation GetServerInformation(MailAccount account, AccountCreationDialogResult dialogResult)
|
||||||
{
|
{
|
||||||
CustomServerInformation resolvedConfig = null;
|
CustomServerInformation resolvedConfig = null;
|
||||||
|
var details = dialogResult.SpecialImapProviderDetails;
|
||||||
|
|
||||||
if (dialogResult.SpecialImapProviderDetails.SpecialImapProvider == SpecialImapProvider.iCloud)
|
if (details.SpecialImapProvider == SpecialImapProvider.iCloud)
|
||||||
{
|
{
|
||||||
resolvedConfig = iCloudServerConfig;
|
resolvedConfig = new CustomServerInformation()
|
||||||
|
{
|
||||||
|
IncomingServer = "imap.mail.me.com",
|
||||||
|
IncomingServerPort = "993",
|
||||||
|
IncomingServerType = CustomIncomingServerType.IMAP4,
|
||||||
|
IncomingServerSocketOption = ImapConnectionSecurity.Auto,
|
||||||
|
IncomingAuthenticationMethod = ImapAuthenticationMethod.Auto,
|
||||||
|
OutgoingServer = "smtp.mail.me.com",
|
||||||
|
OutgoingServerPort = "587",
|
||||||
|
OutgoingServerSocketOption = ImapConnectionSecurity.Auto,
|
||||||
|
OutgoingAuthenticationMethod = ImapAuthenticationMethod.Auto,
|
||||||
|
MaxConcurrentClients = 5,
|
||||||
|
CalDavServiceUrl = "https://caldav.icloud.com/"
|
||||||
|
};
|
||||||
|
|
||||||
// iCloud takes username before the @icloud part for incoming, but full address as outgoing.
|
// iCloud takes username before the @icloud part for incoming, but full address as outgoing.
|
||||||
resolvedConfig.IncomingServerUsername = dialogResult.SpecialImapProviderDetails.Address.Split('@')[0];
|
resolvedConfig.IncomingServerUsername = details.Address.Split('@')[0];
|
||||||
resolvedConfig.OutgoingServerUsername = dialogResult.SpecialImapProviderDetails.Address;
|
resolvedConfig.OutgoingServerUsername = details.Address;
|
||||||
}
|
}
|
||||||
else if (dialogResult.SpecialImapProviderDetails.SpecialImapProvider == SpecialImapProvider.Yahoo)
|
else if (details.SpecialImapProvider == SpecialImapProvider.Yahoo)
|
||||||
{
|
{
|
||||||
resolvedConfig = yahooServerConfig;
|
resolvedConfig = new CustomServerInformation()
|
||||||
|
{
|
||||||
|
IncomingServer = "imap.mail.yahoo.com",
|
||||||
|
IncomingServerPort = "993",
|
||||||
|
IncomingServerType = CustomIncomingServerType.IMAP4,
|
||||||
|
IncomingServerSocketOption = ImapConnectionSecurity.Auto,
|
||||||
|
IncomingAuthenticationMethod = ImapAuthenticationMethod.Auto,
|
||||||
|
OutgoingServer = "smtp.mail.yahoo.com",
|
||||||
|
OutgoingServerPort = "587",
|
||||||
|
OutgoingServerSocketOption = ImapConnectionSecurity.Auto,
|
||||||
|
OutgoingAuthenticationMethod = ImapAuthenticationMethod.Auto,
|
||||||
|
MaxConcurrentClients = 5,
|
||||||
|
CalDavServiceUrl = "https://caldav.calendar.yahoo.com/"
|
||||||
|
};
|
||||||
|
|
||||||
// Yahoo uses full address for both incoming and outgoing.
|
// Yahoo uses full address for both incoming and outgoing.
|
||||||
resolvedConfig.IncomingServerUsername = dialogResult.SpecialImapProviderDetails.Address;
|
resolvedConfig.IncomingServerUsername = details.Address;
|
||||||
resolvedConfig.OutgoingServerUsername = dialogResult.SpecialImapProviderDetails.Address;
|
resolvedConfig.OutgoingServerUsername = details.Address;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill in account details.
|
// Fill in account details.
|
||||||
resolvedConfig.Address = dialogResult.SpecialImapProviderDetails.Address;
|
resolvedConfig.Address = details.Address;
|
||||||
resolvedConfig.IncomingServerPassword = dialogResult.SpecialImapProviderDetails.Password;
|
resolvedConfig.IncomingServerPassword = details.Password;
|
||||||
resolvedConfig.OutgoingServerPassword = dialogResult.SpecialImapProviderDetails.Password;
|
resolvedConfig.OutgoingServerPassword = details.Password;
|
||||||
resolvedConfig.DisplayName = dialogResult.SpecialImapProviderDetails.SenderName;
|
resolvedConfig.DisplayName = details.SenderName;
|
||||||
|
resolvedConfig.CalendarSupportMode = details.CalendarSupportMode;
|
||||||
|
resolvedConfig.CalDavUsername = details.Address;
|
||||||
|
resolvedConfig.CalDavPassword = details.Password;
|
||||||
|
|
||||||
|
if (details.CalendarSupportMode != ImapCalendarSupportMode.CalDav)
|
||||||
|
{
|
||||||
|
resolvedConfig.CalDavServiceUrl = string.Empty;
|
||||||
|
resolvedConfig.CalDavUsername = string.Empty;
|
||||||
|
resolvedConfig.CalDavPassword = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
return resolvedConfig;
|
return resolvedConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user