diff --git a/Directory.Packages.props b/Directory.Packages.props
index e0dee889..d824c2f3 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -16,25 +16,24 @@
-
-
+
-
-
+
+
-
+
-
-
+
+
-
+
@@ -52,21 +51,21 @@
-
+
-
-
+
+
-
-
+
+
@@ -74,7 +73,7 @@
-
+
-
\ No newline at end of file
+
diff --git a/Wino.Core.Tests/CalendarPageViewModelTests.cs b/Wino.Core.Tests/CalendarPageViewModelTests.cs
index 95239be5..021eb16e 100644
--- a/Wino.Core.Tests/CalendarPageViewModelTests.cs
+++ b/Wino.Core.Tests/CalendarPageViewModelTests.cs
@@ -282,6 +282,7 @@ public class CalendarPageViewModelTests
public IEnumerable ActiveCalendars => _activeCalendars;
public IEnumerable AllCalendars => _calendars;
+ public bool IsAnySynchronizationInProgress => false;
public ReadOnlyObservableGroupedCollection GroupedCalendars { get; set; } = null!;
public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar) => _groupedCalendars.Add(groupedAccountCalendar);
diff --git a/Wino.Core/Misc/ColorHelpers.cs b/Wino.Core/Misc/ColorHelpers.cs
index 8b012431..c6ae4450 100644
--- a/Wino.Core/Misc/ColorHelpers.cs
+++ b/Wino.Core/Misc/ColorHelpers.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Drawing;
using System.Globalization;
using System.Linq;
using Wino.Core.Domain.Misc;
@@ -46,25 +45,21 @@ public static class ColorHelpers
return "#FFFFFF";
}
- var color = ColorTranslator.FromHtml(normalizedColor);
- var luminance = ((0.299 * color.R) + (0.587 * color.G) + (0.114 * color.B)) / 255d;
+ var (red, green, blue) = ParseRgb(normalizedColor);
+ var luminance = ((0.299 * red) + (0.587 * green) + (0.114 * blue)) / 255d;
return luminance > 0.6 ? "#111111" : "#FFFFFF";
}
- 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})";
private static string AdjustColor(string hexColor, int cycle)
{
- var color = ColorTranslator.FromHtml(hexColor);
+ var (red, green, blue) = ParseRgb(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));
+ var adjustedRed = (int)Math.Clamp(red * factor, 0, 255);
+ var adjustedGreen = (int)Math.Clamp(green * factor, 0, 255);
+ var adjustedBlue = (int)Math.Clamp(blue * factor, 0, 255);
- return adjusted.ToHexString();
+ return $"#{adjustedRed:X2}{adjustedGreen:X2}{adjustedBlue:X2}";
}
private static bool TryNormalizeHexColor(string value, out string normalized)
@@ -97,4 +92,18 @@ public static class ColorHelpers
private static string NormalizeHexColor(string value)
=> TryNormalizeHexColor(value, out var normalized) ? normalized : string.Empty;
+
+ private static (int Red, int Green, int Blue) ParseRgb(string hexColor)
+ {
+ var normalized = NormalizeHexColor(hexColor);
+ if (string.IsNullOrWhiteSpace(normalized))
+ {
+ return (255, 255, 255);
+ }
+
+ return (
+ Convert.ToInt32(normalized.Substring(1, 2), 16),
+ Convert.ToInt32(normalized.Substring(3, 2), 16),
+ Convert.ToInt32(normalized.Substring(5, 2), 16));
+ }
}
diff --git a/Wino.Mail.WinUI/App.xaml b/Wino.Mail.WinUI/App.xaml
index 21d04fe6..066caab5 100644
--- a/Wino.Mail.WinUI/App.xaml
+++ b/Wino.Mail.WinUI/App.xaml
@@ -19,7 +19,7 @@
-
+
diff --git a/Wino.Mail.WinUI/App.xaml.cs b/Wino.Mail.WinUI/App.xaml.cs
index a9391263..35ed1f86 100644
--- a/Wino.Mail.WinUI/App.xaml.cs
+++ b/Wino.Mail.WinUI/App.xaml.cs
@@ -8,7 +8,6 @@ using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Toolkit.Uwp.Notifications;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media.Animation;
@@ -492,7 +491,7 @@ public partial class App : WinoApplication,
///
private async Task HandleToastActivationAsync(AppNotificationActivatedEventArgs toastArgs)
{
- var toastArguments = ToastArguments.Parse(toastArgs.Argument);
+ var toastArguments = NotificationArguments.Parse(toastArgs.Argument);
if (toastArguments.TryGetValue(Constants.ToastStoreUpdateActionKey, out string storeUpdateAction) &&
storeUpdateAction == Constants.ToastStoreUpdateActionInstall)
diff --git a/Wino.Mail.WinUI/Controls/AiActionsPanel.xaml b/Wino.Mail.WinUI/Controls/AiActionsPanel.xaml
index cf49e1f9..dafe36a9 100644
--- a/Wino.Mail.WinUI/Controls/AiActionsPanel.xaml
+++ b/Wino.Mail.WinUI/Controls/AiActionsPanel.xaml
@@ -34,7 +34,7 @@
+ Text="{x:Bind domain:Translator.AiActions_CheckingStatus}" />
@@ -77,12 +77,12 @@
@@ -95,12 +95,12 @@
+ Content="{x:Bind domain:Translator.Buttons_CreateAccount}" />
@@ -113,24 +113,24 @@
Background="{ThemeResource CardBackgroundFillColorTertiaryBrush}"
CornerRadius="12">
-
+
-
+
-
+
@@ -139,7 +139,7 @@
@@ -162,7 +162,7 @@
+ Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureTranslate}">
@@ -170,7 +170,7 @@
+ Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureRewrite}">
@@ -178,7 +178,7 @@
+ Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureSummarize}">
@@ -202,7 +202,7 @@
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
- Text="{x:Bind domain:Translator.Composer_AiTranslateLanguage, Mode=OneWay}" />
+ Text="{x:Bind domain:Translator.Composer_AiTranslateLanguage}" />
@@ -230,7 +230,7 @@
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
- Text="{x:Bind domain:Translator.Composer_AiRewriteMode, Mode=OneWay}" />
+ Text="{x:Bind domain:Translator.Composer_AiRewriteMode}" />
@@ -269,7 +269,7 @@
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
- Text="{x:Bind domain:Translator.Composer_AiTranslateLanguage, Mode=OneWay}" />
+ Text="{x:Bind domain:Translator.Composer_AiTranslateLanguage}" />
diff --git a/Wino.Mail.WinUI/Controls/OperationCommandBar.cs b/Wino.Mail.WinUI/Controls/OperationCommandBar.cs
index f5233952..969e19f0 100644
--- a/Wino.Mail.WinUI/Controls/OperationCommandBar.cs
+++ b/Wino.Mail.WinUI/Controls/OperationCommandBar.cs
@@ -285,7 +285,7 @@ public sealed partial class OperationCommandBar : CommandBar
{
var button = LoadCommandBarElementTemplate(
MailOperationTemplateKey,
- new MenuOperationCommandBarItemViewModel(
+ new OperationCommandBarMenuOperationItemViewModel(
mailOperationItem,
XamlHelpers.GetOperationString(mailOperationItem.Operation),
XamlHelpers.GetWinoIconGlyph(mailOperationItem.Operation),
@@ -307,7 +307,7 @@ public sealed partial class OperationCommandBar : CommandBar
var label = XamlHelpers.GetOperationString(folderOperationItem.Operation);
var button = LoadCommandBarElementTemplate(
FolderOperationTemplateKey,
- new MenuOperationCommandBarItemViewModel(
+ new OperationCommandBarMenuOperationItemViewModel(
folderOperationItem,
label,
XamlHelpers.GetPathGeometry(folderOperationItem.Operation),
@@ -331,7 +331,7 @@ public sealed partial class OperationCommandBar : CommandBar
{
var button = (AppBarToggleButton)LoadCommandBarElementTemplate(
AIActionsTemplateKey,
- new AIActionsCommandBarItemViewModel(Translator.Composer_AiActions, "\uE945"));
+ new OperationCommandBarAIActionsItemViewModel(Translator.Composer_AiActions, "\uE945"));
button.SetBinding(AppBarToggleButton.IsCheckedProperty, new Binding
{
@@ -350,7 +350,7 @@ public sealed partial class OperationCommandBar : CommandBar
var button = (AppBarButton)LoadCommandBarElementTemplate(
ThemeToggleTemplateKey,
- new ThemeCommandBarItemViewModel(label, icon));
+ new OperationCommandBarThemeItemViewModel(label, icon));
button.Click += ThemeButton_Click;
return button;
@@ -430,48 +430,48 @@ public sealed partial class OperationCommandBar : CommandBar
: CommandBarOverflowButtonVisibility.Auto;
}
- private sealed class MenuOperationCommandBarItemViewModel
- {
- public MenuOperationCommandBarItemViewModel(IMenuOperation operation, string label, WinoIconGlyph icon, CommandBarLabelPosition labelPosition)
- {
- Operation = operation;
- Label = label;
- Icon = icon;
- ToolTip = label;
- LabelPosition = labelPosition;
- }
-
- public IMenuOperation Operation { get; }
- public string Label { get; }
- public WinoIconGlyph Icon { get; }
- public string ToolTip { get; }
- public bool IsEnabled => Operation.IsEnabled;
- public CommandBarLabelPosition LabelPosition { get; }
- }
-
- private sealed class AIActionsCommandBarItemViewModel
- {
- public AIActionsCommandBarItemViewModel(string toolTip, string glyph)
- {
- ToolTip = toolTip;
- Glyph = glyph;
- }
-
- public string ToolTip { get; }
- public string Glyph { get; }
- }
-
- private sealed class ThemeCommandBarItemViewModel
- {
- public ThemeCommandBarItemViewModel(string toolTip, WinoIconGlyph icon)
- {
- ToolTip = toolTip;
- Icon = icon;
- }
-
- public string ToolTip { get; }
- public WinoIconGlyph Icon { get; }
- }
-
private sealed class SeparatorCommandBarItemViewModel;
}
+
+public sealed class OperationCommandBarMenuOperationItemViewModel
+{
+ public OperationCommandBarMenuOperationItemViewModel(IMenuOperation operation, string label, WinoIconGlyph icon, CommandBarLabelPosition labelPosition)
+ {
+ Operation = operation;
+ Label = label;
+ Icon = icon;
+ ToolTip = label;
+ LabelPosition = labelPosition;
+ }
+
+ public IMenuOperation Operation { get; }
+ public string Label { get; }
+ public WinoIconGlyph Icon { get; }
+ public string ToolTip { get; }
+ public bool IsEnabled => Operation.IsEnabled;
+ public CommandBarLabelPosition LabelPosition { get; }
+}
+
+public sealed class OperationCommandBarAIActionsItemViewModel
+{
+ public OperationCommandBarAIActionsItemViewModel(string toolTip, string glyph)
+ {
+ ToolTip = toolTip;
+ Glyph = glyph;
+ }
+
+ public string ToolTip { get; }
+ public string Glyph { get; }
+}
+
+public sealed class OperationCommandBarThemeItemViewModel
+{
+ public OperationCommandBarThemeItemViewModel(string toolTip, WinoIconGlyph icon)
+ {
+ ToolTip = toolTip;
+ Icon = icon;
+ }
+
+ public string ToolTip { get; }
+ public WinoIconGlyph Icon { get; }
+}
diff --git a/Wino.Mail.WinUI/Dialogs/PrintDialog.xaml.cs b/Wino.Mail.WinUI/Dialogs/PrintDialog.xaml.cs
index 75b7768b..13b9cfb2 100644
--- a/Wino.Mail.WinUI/Dialogs/PrintDialog.xaml.cs
+++ b/Wino.Mail.WinUI/Dialogs/PrintDialog.xaml.cs
@@ -1,11 +1,11 @@
using System.Collections.Generic;
-using System.Drawing.Printing;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Controls;
using Serilog;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Printing;
+using Wino.Mail.WinUI.Helpers;
namespace Wino.Mail.WinUI.Dialogs;
@@ -87,15 +87,7 @@ public sealed partial class PrintDialog : ContentDialog
{
var printers = await Task.Run(() =>
{
- var printerList = new List();
-
- // Get all installed printers using System.Drawing.Printing
- foreach (string printerName in PrinterSettings.InstalledPrinters)
- {
- printerList.Add(printerName);
- }
-
- return printerList.AsEnumerable();
+ return InstalledPrinterHelper.GetInstalledPrinters().AsEnumerable();
});
SetAvailablePrinters(printers);
diff --git a/Wino.Mail.WinUI/Helpers/CalendarXamlHelpers.cs b/Wino.Mail.WinUI/Helpers/CalendarXamlHelpers.cs
index cc928bab..70f085a8 100644
--- a/Wino.Mail.WinUI/Helpers/CalendarXamlHelpers.cs
+++ b/Wino.Mail.WinUI/Helpers/CalendarXamlHelpers.cs
@@ -76,7 +76,9 @@ public static class CalendarXamlHelpers
interval: recurrenceRule.Interval <= 0 ? 1 : recurrenceRule.Interval,
frequency: frequency.Value,
daysOfWeek: recurrenceRule.ByDay?.Select(day => day.DayOfWeek).ToList() ?? [],
- recurrenceEndDate: recurrenceRule.Until == default ? null : new DateTimeOffset(recurrenceRule.Until));
+ recurrenceEndDate: recurrenceRule.Until == null
+ ? null
+ : new DateTimeOffset(DateTime.SpecifyKind(recurrenceRule.Until.AsUtc, DateTimeKind.Utc)));
}
public static string GetDetailsPopupDurationString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings)
diff --git a/Wino.Mail.WinUI/Helpers/InstalledPrinterHelper.cs b/Wino.Mail.WinUI/Helpers/InstalledPrinterHelper.cs
new file mode 100644
index 00000000..34d629ed
--- /dev/null
+++ b/Wino.Mail.WinUI/Helpers/InstalledPrinterHelper.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace Wino.Mail.WinUI.Helpers;
+
+internal static partial class InstalledPrinterHelper
+{
+ private const int PrinterEnumLocal = 0x00000002;
+ private const int PrinterEnumConnections = 0x00000004;
+
+ public static IReadOnlyList GetInstalledPrinters()
+ {
+ var flags = PrinterEnumLocal | PrinterEnumConnections;
+
+ if (!EnumPrinters(flags, null, 4, IntPtr.Zero, 0, out var bytesNeeded, out _))
+ {
+ var error = Marshal.GetLastWin32Error();
+ if (error != 122 || bytesNeeded <= 0)
+ {
+ throw new InvalidOperationException($"EnumPrinters failed to query buffer size. Win32 error: {error}.");
+ }
+ }
+
+ var printerBuffer = Marshal.AllocHGlobal(bytesNeeded);
+ try
+ {
+ if (!EnumPrinters(flags, null, 4, printerBuffer, bytesNeeded, out _, out var printerCount))
+ {
+ throw new InvalidOperationException($"EnumPrinters failed to enumerate printers. Win32 error: {Marshal.GetLastWin32Error()}.");
+ }
+
+ var printers = new List(printerCount);
+ var structSize = Marshal.SizeOf();
+
+ for (var i = 0; i < printerCount; i++)
+ {
+ var current = IntPtr.Add(printerBuffer, i * structSize);
+ var info = Marshal.PtrToStructure(current);
+ if (!string.IsNullOrWhiteSpace(info.PrinterName))
+ {
+ printers.Add(info.PrinterName);
+ }
+ }
+
+ return printers;
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(printerBuffer);
+ }
+ }
+
+ [LibraryImport("winspool.drv", EntryPoint = "EnumPrintersW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static partial bool EnumPrinters(
+ int flags,
+ string? name,
+ int level,
+ IntPtr pPrinterEnum,
+ int cbBuf,
+ out int pcbNeeded,
+ out int pcReturned);
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct PrinterInfo4
+ {
+ public nint PrinterNamePointer;
+ public nint ServerNamePointer;
+ public int Attributes;
+
+ public string? PrinterName => Marshal.PtrToStringUni(PrinterNamePointer);
+ }
+}
diff --git a/Wino.Mail.WinUI/NotificationArguments.cs b/Wino.Mail.WinUI/NotificationArguments.cs
new file mode 100644
index 00000000..c45c7ab9
--- /dev/null
+++ b/Wino.Mail.WinUI/NotificationArguments.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+
+namespace Wino.Mail.WinUI;
+
+internal sealed class NotificationArguments
+{
+ private static readonly NotificationArguments Empty = new(new Dictionary(StringComparer.Ordinal));
+
+ private readonly IReadOnlyDictionary _values;
+
+ private NotificationArguments(IReadOnlyDictionary values)
+ {
+ _values = values;
+ }
+
+ public string this[string key] => _values[key];
+
+ public static NotificationArguments Parse(string? encodedArguments)
+ {
+ if (string.IsNullOrWhiteSpace(encodedArguments))
+ return Empty;
+
+ var values = new Dictionary(StringComparer.Ordinal);
+
+ foreach (var pair in encodedArguments.Split('&', StringSplitOptions.RemoveEmptyEntries))
+ {
+ var separatorIndex = pair.IndexOf('=');
+ if (separatorIndex < 0)
+ {
+ values[WebUtility.UrlDecode(pair)] = string.Empty;
+ continue;
+ }
+
+ var key = WebUtility.UrlDecode(pair[..separatorIndex]);
+ var value = WebUtility.UrlDecode(pair[(separatorIndex + 1)..]);
+
+ values[key] = value;
+ }
+
+ return new NotificationArguments(values);
+ }
+
+ public bool TryGetValue(string key, out string value)
+ => _values.TryGetValue(key, out value!);
+
+ public bool TryGetValue(string key, out TEnum value) where TEnum : struct, Enum
+ {
+ value = default;
+
+ return _values.TryGetValue(key, out var rawValue) &&
+ Enum.TryParse(rawValue, ignoreCase: true, out value);
+ }
+}
diff --git a/Wino.Mail.WinUI/Package.appxmanifest b/Wino.Mail.WinUI/Package.appxmanifest
index b428feb5..843427f6 100644
--- a/Wino.Mail.WinUI/Package.appxmanifest
+++ b/Wino.Mail.WinUI/Package.appxmanifest
@@ -23,7 +23,7 @@
+ Version="2.0.0.0" />
diff --git a/Wino.Mail.WinUI/Program.cs b/Wino.Mail.WinUI/Program.cs
index 76b264be..fe1dba23 100644
--- a/Wino.Mail.WinUI/Program.cs
+++ b/Wino.Mail.WinUI/Program.cs
@@ -3,7 +3,6 @@ using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.Toolkit.Uwp.Notifications;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.Windows.AppNotifications;
@@ -206,7 +205,7 @@ public class Program
return true;
}
- var toastArguments = ToastArguments.Parse(toastArgs.Argument);
+ var toastArguments = NotificationArguments.Parse(toastArgs.Argument);
if (toastArguments.TryGetValue(Constants.ToastStoreUpdateActionKey, out string storeUpdateAction) &&
storeUpdateAction == Constants.ToastStoreUpdateActionInstall)
diff --git a/Wino.Mail.WinUI/Services/NotificationBuilder.cs b/Wino.Mail.WinUI/Services/NotificationBuilder.cs
index 529b872f..9a958127 100644
--- a/Wino.Mail.WinUI/Services/NotificationBuilder.cs
+++ b/Wino.Mail.WinUI/Services/NotificationBuilder.cs
@@ -4,7 +4,8 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging;
-using Microsoft.Toolkit.Uwp.Notifications;
+using Microsoft.Windows.AppNotifications;
+using Microsoft.Windows.AppNotifications.Builder;
using Serilog;
using Windows.Data.Xml.Dom;
using Windows.UI.Notifications;
@@ -85,23 +86,17 @@ public class NotificationBuilder : INotificationBuilder
if (mailCount == 0)
return;
- // If there are more than 3 mails, just display 1 general toast.
+ // If there are more than 3 mails, just display 1 general notification.
if (mailCount > 3)
{
- var builder = new ToastContentBuilder();
- builder.SetToastScenario(ToastScenario.Default);
+ var builder = CreateBuilder();
builder.AddText(Translator.Notifications_MultipleNotificationsTitle);
builder.AddText(string.Format(Translator.Notifications_MultipleNotificationsMessage, mailCount));
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
+ builder.SetAudioUri(new Uri("ms-winsoundevent:Notification.Mail"));
- builder.AddButton(GetDismissButton());
- builder.AddAudio(new ToastAudio()
- {
- Src = new Uri("ms-winsoundevent:Notification.Mail")
- });
-
- ShowToast(builder);
+ ShowNotification(builder);
}
else
{
@@ -121,8 +116,7 @@ public class NotificationBuilder : INotificationBuilder
private async Task CreateSingleNotificationAsync(MailCopy mailItem)
{
- var builder = new ToastContentBuilder();
- builder.SetToastScenario(ToastScenario.Default);
+ var builder = CreateBuilder();
var avatarThumbnail = await _thumbnailService.GetThumbnailAsync(mailItem.FromAddress, awaitLoad: true);
if (!string.IsNullOrEmpty(avatarThumbnail))
@@ -133,63 +127,47 @@ public class NotificationBuilder : INotificationBuilder
var bytes = Convert.FromBase64String(avatarThumbnail);
await stream.WriteAsync(bytes);
}
- builder.AddAppLogoOverride(new Uri($"ms-appdata:///temp/{tempFile.Name}"), hintCrop: ToastGenericAppLogoCrop.Default);
+
+ builder.SetAppLogoOverride(new Uri($"ms-appdata:///temp/{tempFile.Name}"), AppNotificationImageCrop.Default);
}
- // Override system notification timestamp with received date of the mail.
- builder.AddCustomTimeStamp(mailItem.CreationDate.ToLocalTime());
-
+ builder.SetTimeStamp(mailItem.CreationDate.ToLocalTime());
builder.AddText(mailItem.FromName);
builder.AddText(mailItem.Subject);
builder.AddText(mailItem.PreviewText);
builder.AddArgument(Constants.ToastMailUniqueIdKey, mailItem.UniqueId.ToString());
- builder.AddArgument(Constants.ToastActionKey, MailOperation.Navigate);
+ builder.AddArgument(Constants.ToastActionKey, MailOperation.Navigate.ToString());
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
builder.AddButton(GetMarkAsReadButton(mailItem.UniqueId));
builder.AddButton(GetDeleteButton(mailItem.UniqueId));
builder.AddButton(GetArchiveButton(mailItem.UniqueId));
- builder.AddAudio(new ToastAudio()
- {
- Src = new Uri("ms-winsoundevent:Notification.Mail")
- });
+ builder.SetAudioUri(new Uri("ms-winsoundevent:Notification.Mail"));
- // Use UniqueId as tag to allow removal
- ShowToast(builder, mailItem.UniqueId.ToString());
+ ShowNotification(builder, mailItem.UniqueId.ToString());
}
- private ToastButton GetDismissButton()
- => new ToastButton()
- .SetDismissActivation()
- .SetImageUri(GetNotificationIconUri("dismiss"));
-
- private ToastButton GetArchiveButton(Guid mailUniqueId)
- => new ToastButton()
- .SetContent(Translator.MailOperation_Archive)
- .SetImageUri(GetNotificationIconUri("mail-archive"))
+ private AppNotificationButton GetArchiveButton(Guid mailUniqueId)
+ => new AppNotificationButton(Translator.MailOperation_Archive)
+ .SetIcon(GetNotificationIconUri("mail-archive"))
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
- .AddArgument(Constants.ToastActionKey, MailOperation.Archive)
- .AddArgument(Constants.ToastModeKey, Constants.ToastModeMail)
- .SetBackgroundActivation();
+ .AddArgument(Constants.ToastActionKey, MailOperation.Archive.ToString())
+ .AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
- private ToastButton GetDeleteButton(Guid mailUniqueId)
- => new ToastButton()
- .SetContent(Translator.MailOperation_Delete)
- .SetImageUri(GetNotificationIconUri("mail-delete"))
+ private AppNotificationButton GetDeleteButton(Guid mailUniqueId)
+ => new AppNotificationButton(Translator.MailOperation_Delete)
+ .SetIcon(GetNotificationIconUri("mail-delete"))
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
- .AddArgument(Constants.ToastActionKey, MailOperation.SoftDelete)
- .AddArgument(Constants.ToastModeKey, Constants.ToastModeMail)
- .SetBackgroundActivation();
+ .AddArgument(Constants.ToastActionKey, MailOperation.SoftDelete.ToString())
+ .AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
- private ToastButton GetMarkAsReadButton(Guid mailUniqueId)
- => new ToastButton()
- .SetContent(Translator.MailOperation_MarkAsRead)
- .SetImageUri(GetNotificationIconUri("mail-markread"))
+ private AppNotificationButton GetMarkAsReadButton(Guid mailUniqueId)
+ => new AppNotificationButton(Translator.MailOperation_MarkAsRead)
+ .SetIcon(GetNotificationIconUri("mail-markread"))
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
- .AddArgument(Constants.ToastActionKey, MailOperation.MarkAsRead)
- .AddArgument(Constants.ToastModeKey, Constants.ToastModeMail)
- .SetBackgroundActivation();
+ .AddArgument(Constants.ToastActionKey, MailOperation.MarkAsRead.ToString())
+ .AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
public async Task UpdateTaskbarIconBadgeAsync()
{
@@ -216,26 +194,24 @@ public class NotificationBuilder : INotificationBuilder
if (totalUnreadCount > 0)
{
- // Get the blank badge XML payload for a badge number
XmlDocument badgeXml = BadgeUpdateManager.GetTemplateContent(BadgeTemplateType.BadgeNumber);
- // Set the value of the badge in the XML to our number
XmlElement? badgeElement = badgeXml.SelectSingleNode("/badge") as XmlElement;
if (badgeElement == null)
{
badgeUpdater.Clear();
return;
}
+
badgeElement.SetAttribute("value", totalUnreadCount.ToString());
- // Create the badge notification
- BadgeNotification badge = new BadgeNotification(badgeXml);
-
- // And update the badge
+ BadgeNotification badge = new(badgeXml);
badgeUpdater.Update(badge);
}
else
+ {
badgeUpdater.Clear();
+ }
}
catch (Exception ex)
{
@@ -247,7 +223,7 @@ public class NotificationBuilder : INotificationBuilder
{
try
{
- ToastNotificationManager.History.Remove(mailUniqueId.ToString(), null);
+ AppNotificationManager.Default.RemoveByTagAsync(mailUniqueId.ToString()).AsTask().GetAwaiter().GetResult();
}
catch (ArgumentException)
{
@@ -261,44 +237,39 @@ public class NotificationBuilder : INotificationBuilder
public void CreateAttentionRequiredNotification(MailAccount account)
{
- var builder = new ToastContentBuilder();
- builder.SetToastScenario(ToastScenario.Default);
+ var builder = CreateBuilder();
builder.AddText(Translator.Exception_AccountNeedsAttention_Title);
builder.AddText(string.Format(Translator.Exception_AccountNeedsAttention_Message, account.Name));
-
- builder.AddButton(GetDismissButton());
-
builder.AddArgument(Constants.ToastMailAccountIdKey, account.Id.ToString());
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
- builder.AddButton(new ToastButton().SetContent(Translator.Buttons_FixAccount));
- ShowToast(builder);
+ builder.AddButton(new AppNotificationButton(Translator.Buttons_FixAccount)
+ .AddArgument(Constants.ToastMailAccountIdKey, account.Id.ToString())
+ .AddArgument(Constants.ToastModeKey, Constants.ToastModeMail));
+
+ ShowNotification(builder);
}
public void CreateWebView2RuntimeMissingNotification()
{
- var builder = new ToastContentBuilder();
- builder.SetToastScenario(ToastScenario.Default);
+ var builder = CreateBuilder();
builder.AddText(Translator.Exception_WebView2RuntimeMissing_Title);
builder.AddText(Translator.Exception_WebView2RuntimeMissing_Message);
-
- builder.AddButton(GetDismissButton());
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
- ShowToast(builder);
+
+ ShowNotification(builder);
}
public void CreateStoreUpdateNotification()
{
- var builder = new ToastContentBuilder();
- builder.SetToastScenario(ToastScenario.Default);
+ var builder = CreateBuilder();
builder.AddText(Translator.Notifications_StoreUpdateAvailableTitle);
builder.AddText(Translator.Notifications_StoreUpdateAvailableMessage);
builder.AddArgument(Constants.ToastStoreUpdateActionKey, Constants.ToastStoreUpdateActionInstall);
- builder.AddButton(GetDismissButton());
- ShowToast(builder, "store-update-available");
+ ShowNotification(builder, "store-update-available");
}
public Task CreateCalendarReminderNotificationAsync(CalendarItem calendarItem, long reminderDurationInSeconds)
@@ -306,8 +277,7 @@ public class NotificationBuilder : INotificationBuilder
if (calendarItem == null)
return Task.CompletedTask;
- var builder = new ToastContentBuilder();
- builder.SetToastScenario(ToastScenario.Reminder);
+ var builder = CreateBuilder(AppNotificationScenario.Reminder);
var localStart = calendarItem.GetLocalStartDate();
var nowLocal = DateTime.Now;
@@ -334,49 +304,38 @@ public class NotificationBuilder : INotificationBuilder
? preferredSnoozeMinutes
: allowedSnoozeMinutes[0];
- var selectionBox = new ToastSelectionBox(Constants.ToastCalendarSnoozeDurationInputId)
- {
- DefaultSelectionBoxItemId = defaultSnoozeMinutes.ToString()
- };
+ var selectionBox = new AppNotificationComboBox(Constants.ToastCalendarSnoozeDurationInputId)
+ .SetSelectedItem(defaultSnoozeMinutes.ToString());
foreach (var snoozeMinutes in allowedSnoozeMinutes)
{
- selectionBox.Items.Add(new ToastSelectionBoxItem(
+ selectionBox.AddItem(
snoozeMinutes.ToString(),
- string.Format(Translator.CalendarReminder_SnoozeMinutesOption, snoozeMinutes)));
+ string.Format(Translator.CalendarReminder_SnoozeMinutesOption, snoozeMinutes));
}
- builder.AddToastInput(selectionBox);
- var snoozeButton = new ToastButton()
- .SetContent(Translator.CalendarReminder_SnoozeAction)
- .SetImageUri(GetNotificationIconUri("calendar-snooze"))
+ builder.AddComboBox(selectionBox);
+ builder.AddButton(new AppNotificationButton(Translator.CalendarReminder_SnoozeAction)
.AddArgument(Constants.ToastCalendarActionKey, Constants.ToastCalendarSnoozeAction)
.AddArgument(Constants.ToastCalendarItemIdKey, calendarItem.Id.ToString())
- .AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar)
- .SetBackgroundActivation();
-
- builder.AddButton(snoozeButton);
+ .AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar));
}
- builder.AddButton(new ToastButton()
- .SetDismissActivation()
- .SetImageUri(GetNotificationIconUri("dismiss")));
+ builder.AddButton(new AppNotificationButton(Translator.Buttons_Open)
+ .AddArgument(Constants.ToastCalendarActionKey, Constants.ToastCalendarNavigateAction)
+ .AddArgument(Constants.ToastCalendarItemIdKey, calendarItem.Id.ToString())
+ .AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar));
if (Uri.TryCreate(calendarItem.HtmlLink, UriKind.Absolute, out var joinUri))
{
- builder.AddButton(new ToastButton()
- .SetContent(Translator.CalendarEventDetails_JoinOnline)
- .SetImageUri(GetNotificationIconUri("calendar-join"))
- .SetProtocolActivation(joinUri));
+ builder.AddButton(new AppNotificationButton(Translator.CalendarEventDetails_JoinOnline)
+ .SetInvokeUri(joinUri));
}
- builder.AddAudio(new ToastAudio()
- {
- Src = new Uri("ms-winsoundevent:Notification.Reminder")
- });
+ builder.SetAudioUri(new Uri("ms-winsoundevent:Notification.Reminder"));
var tag = $"calendar-reminder-{calendarItem.Id:N}-{reminderDurationInSeconds}";
- ShowToast(builder, tag);
+ ShowNotification(builder, tag);
return Task.CompletedTask;
}
@@ -411,17 +370,17 @@ public class NotificationBuilder : INotificationBuilder
return string.Format(Translator.CalendarReminder_StartedMinutesAgo, minutesAgo);
}
- private static void ShowToast(ToastContentBuilder builder, string? tag = null)
+ private static AppNotificationBuilder CreateBuilder(AppNotificationScenario scenario = AppNotificationScenario.Default)
+ => new AppNotificationBuilder().SetScenario(scenario);
+
+ private static void ShowNotification(AppNotificationBuilder builder, string? tag = null)
{
- var toastNotification = new ToastNotification(builder.GetToastContent().GetXml());
+ var notification = builder.BuildNotification();
if (!string.IsNullOrWhiteSpace(tag))
- {
- toastNotification.Tag = tag;
- }
+ notification.Tag = tag;
- var notifier = ToastNotificationManager.CreateToastNotifier();
- notifier.Show(toastNotification);
+ AppNotificationManager.Default.Show(notification);
}
private Uri GetNotificationIconUri(string iconName)
@@ -430,4 +389,3 @@ public class NotificationBuilder : INotificationBuilder
return new($"{NotificationIconRootUri}{iconName}.png");
}
}
-
diff --git a/Wino.Mail.WinUI/Styles/OperationCommandBar.xaml b/Wino.Mail.WinUI/Styles/OperationCommandBar.xaml
index 77fd8d25..1faa4729 100644
--- a/Wino.Mail.WinUI/Styles/OperationCommandBar.xaml
+++ b/Wino.Mail.WinUI/Styles/OperationCommandBar.xaml
@@ -1,4 +1,5 @@
@@ -10,50 +11,50 @@
-
+
+ IsEnabled="{x:Bind IsEnabled}"
+ Label="{x:Bind Label}"
+ LabelPosition="{x:Bind LabelPosition}"
+ ToolTipService.ToolTip="{x:Bind ToolTip}">
-
+
-
+
+ IsEnabled="{x:Bind IsEnabled}"
+ Label="{x:Bind Label}"
+ LabelPosition="{x:Bind LabelPosition}"
+ ToolTipService.ToolTip="{x:Bind ToolTip}">
-
+
-
+
+ ToolTipService.ToolTip="{x:Bind ToolTip}">
-
+
-
+
+ ToolTipService.ToolTip="{x:Bind ToolTip}">
-
+
diff --git a/Wino.Mail.WinUI/Styles/OperationCommandBar.xaml.cs b/Wino.Mail.WinUI/Styles/OperationCommandBar.xaml.cs
new file mode 100644
index 00000000..bb504c6e
--- /dev/null
+++ b/Wino.Mail.WinUI/Styles/OperationCommandBar.xaml.cs
@@ -0,0 +1,11 @@
+using Microsoft.UI.Xaml;
+
+namespace Wino.Mail.WinUI.Styles;
+
+public partial class OperationCommandBarResources : ResourceDictionary
+{
+ public OperationCommandBarResources()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/Wino.Mail.WinUI/Views/WinoAppShell.xaml b/Wino.Mail.WinUI/Views/WinoAppShell.xaml
index 46421db6..48aa5c4b 100644
--- a/Wino.Mail.WinUI/Views/WinoAppShell.xaml
+++ b/Wino.Mail.WinUI/Views/WinoAppShell.xaml
@@ -101,11 +101,11 @@
BorderThickness="1"
Click="AttentionIconClicked"
Foreground="{ThemeResource InfoBarWarningSeverityIconBackground}"
- ToolTipService.ToolTip="{x:Bind domain:Translator.Info_AccountAttentionRequiredClickableMessage, Mode=OneWay}"
+ ToolTipService.ToolTip="{x:Bind domain:Translator.Info_AccountAttentionRequiredClickableMessage}"
Visibility="{x:Bind IsAttentionRequired, Mode=OneWay}">
-
+
@@ -567,8 +567,8 @@
diff --git a/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj b/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj
index 87965fa0..b9174fd9 100644
--- a/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj
+++ b/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj
@@ -12,30 +12,11 @@
true
true
enable
-
-
- False
- True
- True
- $(NoWarn);CS8305
- true
- False
- True
-
-
- False
- True
-
$(DefineConstants);DISABLE_XAML_GENERATED_MAIN
-
-
-
-
-
@@ -203,7 +184,6 @@
-
@@ -215,7 +195,6 @@
-
diff --git a/Wino.Services/AccountService.cs b/Wino.Services/AccountService.cs
index 8ae13070..142c2bd1 100644
--- a/Wino.Services/AccountService.cs
+++ b/Wino.Services/AccountService.cs
@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
-using System.Drawing;
+using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Diagnostics;
@@ -662,14 +662,40 @@ public class AccountService : BaseDatabaseService, IAccountService
private static string GetReadableTextColorHex(string backgroundColorHex)
{
- if (string.IsNullOrWhiteSpace(backgroundColorHex))
+ if (!TryParseHexColor(backgroundColorHex, out var red, out var green, out var blue))
return "#FFFFFF";
- var color = ColorTranslator.FromHtml(backgroundColorHex);
- var luminance = ((0.299 * color.R) + (0.587 * color.G) + (0.114 * color.B)) / 255d;
+ var luminance = ((0.299 * red) + (0.587 * green) + (0.114 * blue)) / 255d;
return luminance > 0.6 ? "#111111" : "#FFFFFF";
}
+ private static bool TryParseHexColor(string value, out int red, out int green, out int blue)
+ {
+ red = 255;
+ green = 255;
+ blue = 255;
+
+ if (string.IsNullOrWhiteSpace(value))
+ return false;
+
+ var color = value.Trim();
+ if (color.StartsWith('#'))
+ {
+ color = color[1..];
+ }
+
+ if (color.Length != 6 ||
+ !int.TryParse(color, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _))
+ {
+ return false;
+ }
+
+ red = Convert.ToInt32(color.Substring(0, 2), 16);
+ green = Convert.ToInt32(color.Substring(2, 2), 16);
+ blue = Convert.ToInt32(color.Substring(4, 2), 16);
+ return true;
+ }
+
public async Task UpdateAccountOrdersAsync(Dictionary accountIdOrderPair)
{
foreach (var pair in accountIdOrderPair)
diff --git a/Wino.Services/CalDavClient.cs b/Wino.Services/CalDavClient.cs
index 5d110c8b..285a7145 100644
--- a/Wino.Services/CalDavClient.cs
+++ b/Wino.Services/CalDavClient.cs
@@ -421,14 +421,14 @@ public sealed class CalDavClient : ICalDavClient
var result = new List();
var masters = allEvents
- .Where(e => e != null && !string.IsNullOrWhiteSpace(e.Uid) && e.RecurrenceId == null)
+ .Where(e => e != null && !string.IsNullOrWhiteSpace(e.Uid) && GetRecurrenceId(e) == null)
.GroupBy(e => e.Uid, StringComparer.OrdinalIgnoreCase)
.Select(g => g.First())
.ToList();
var exceptionMap = allEvents
- .Where(e => e != null && !string.IsNullOrWhiteSpace(e.Uid) && e.RecurrenceId != null)
- .GroupBy(e => $"{e.Uid}|{GetOccurrenceKey(e.RecurrenceId)}", StringComparer.OrdinalIgnoreCase)
+ .Where(e => e != null && !string.IsNullOrWhiteSpace(e.Uid) && GetRecurrenceId(e) != null)
+ .GroupBy(e => $"{e.Uid}|{GetOccurrenceKey(GetRecurrenceId(e))}", StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
var consumedExceptions = new HashSet(StringComparer.OrdinalIgnoreCase);
@@ -453,7 +453,15 @@ public sealed class CalDavClient : ICalDavClient
seriesMasterRemoteEventId: string.Empty,
recurrence: BuildRecurrenceString(master)));
- var occurrences = master.GetOccurrences(windowStartUtc.UtcDateTime, windowEndUtc.UtcDateTime);
+ var occurrences = master
+ .GetOccurrences(
+ new CalDateTime(windowStartUtc.UtcDateTime, true),
+ new Ical.Net.Evaluation.EvaluationOptions())
+ .Where(o => Overlaps(
+ ToDateTimeOffset(o.Period.StartTime),
+ ToDateTimeOffset(o.Period.EndTime),
+ windowStartUtc,
+ windowEndUtc));
foreach (var occurrence in occurrences)
{
@@ -507,9 +515,10 @@ public sealed class CalDavClient : ICalDavClient
}
}
- foreach (var exceptionEvent in allEvents.Where(e => e != null && e.RecurrenceId != null && !string.IsNullOrWhiteSpace(e.Uid)))
+ foreach (var exceptionEvent in allEvents.Where(e => e != null && GetRecurrenceId(e) != null && !string.IsNullOrWhiteSpace(e.Uid)))
{
- var key = $"{exceptionEvent.Uid}|{GetOccurrenceKey(exceptionEvent.RecurrenceId)}";
+ var recurrenceId = GetRecurrenceId(exceptionEvent);
+ var key = $"{exceptionEvent.Uid}|{GetOccurrenceKey(recurrenceId)}";
if (consumedExceptions.Contains(key))
continue;
@@ -525,7 +534,7 @@ public sealed class CalDavClient : ICalDavClient
sourceEvent: exceptionEvent,
start: start,
end: end,
- remoteEventId: BuildRemoteEventId(exceptionEvent.Uid, GetOccurrenceKey(exceptionEvent.RecurrenceId)),
+ remoteEventId: BuildRemoteEventId(exceptionEvent.Uid, GetOccurrenceKey(recurrenceId)),
resourceHref: resourceHref,
eTag: eTag,
icsContent: icsContent,
@@ -546,16 +555,30 @@ public sealed class CalDavClient : ICalDavClient
private static bool HasRecurrence(CalendarEvent calendarEvent)
=> (calendarEvent.RecurrenceRules?.Any() ?? false)
- || (calendarEvent.RecurrenceDates?.Any() ?? false);
+ || (calendarEvent.RecurrenceDates?.GetAllPeriods().Any() ?? false);
private static string BuildRemoteEventId(string uid, string occurrenceKey)
=> string.IsNullOrWhiteSpace(occurrenceKey) ? uid : $"{uid}::{occurrenceKey}";
- private static string GetOccurrenceKey(IDateTime dateTime)
+ private static CalDateTime GetRecurrenceId(CalendarEvent calendarEvent)
+ => calendarEvent?.RecurrenceIdentifier?.StartTime;
+
+ private static string GetOccurrenceKey(CalDateTime dateTime)
=> dateTime.AsUtc.ToString("yyyyMMdd'T'HHmmss'Z'");
- private static DateTimeOffset ToDateTimeOffset(IDateTime dateTime)
- => dateTime?.AsDateTimeOffset ?? default;
+ private static DateTimeOffset ToDateTimeOffset(CalDateTime dateTime)
+ {
+ if (dateTime == null)
+ return default;
+
+ if (dateTime.IsFloating)
+ {
+ var floatingValue = DateTime.SpecifyKind(dateTime.Value, DateTimeKind.Unspecified);
+ return new DateTimeOffset(floatingValue, TimeZoneInfo.Local.GetUtcOffset(floatingValue));
+ }
+
+ return new DateTimeOffset(DateTime.SpecifyKind(dateTime.AsUtc, DateTimeKind.Utc));
+ }
private static bool Overlaps(DateTimeOffset start, DateTimeOffset end, DateTimeOffset windowStart, DateTimeOffset windowEnd)
{
@@ -601,7 +624,7 @@ public sealed class CalDavClient : ICalDavClient
.Where(a => a?.Trigger != null && a.Trigger.IsRelative && a.Trigger.Duration.HasValue)
.Select(a => new CalDavEventReminder
{
- DurationInSeconds = (int)Math.Abs(a.Trigger.Duration.Value.TotalSeconds),
+ DurationInSeconds = (int)Math.Abs(a.Trigger.Duration.Value.ToTimeSpanUnspecified().TotalSeconds),
ReminderType = string.Equals(a.Action, "EMAIL", StringComparison.OrdinalIgnoreCase)
? CalendarItemReminderType.Email
: CalendarItemReminderType.Popup
@@ -638,7 +661,7 @@ public sealed class CalDavClient : ICalDavClient
};
}
- private static string ResolveTimeZoneId(IDateTime sourceDateTime, DateTimeOffset parsedDateTime)
+ private static string ResolveTimeZoneId(CalDateTime sourceDateTime, DateTimeOffset parsedDateTime)
{
var explicitTimeZoneId = sourceDateTime?.TzId;
if (!string.IsNullOrWhiteSpace(explicitTimeZoneId))
@@ -664,34 +687,26 @@ public sealed class CalDavClient : ICalDavClient
if (sourceEvent.ExceptionDates != null)
{
- foreach (var periodList in sourceEvent.ExceptionDates)
- {
- var dates = periodList
- .Where(p => p.StartTime != null)
- .Select(p => p.StartTime.AsUtc.ToString("yyyyMMdd'T'HHmmss'Z'"))
- .ToList();
+ var dates = sourceEvent.ExceptionDates
+ .GetAllDates()
+ .Where(d => d != null)
+ .Select(d => d.AsUtc.ToString("yyyyMMdd'T'HHmmss'Z'"))
+ .ToList();
- if (dates.Count > 0)
- {
- recurrenceLines.Add($"EXDATE:{string.Join(",", dates)}");
- }
- }
+ if (dates.Count > 0)
+ recurrenceLines.Add($"EXDATE:{string.Join(",", dates)}");
}
if (sourceEvent.RecurrenceDates != null)
{
- foreach (var periodList in sourceEvent.RecurrenceDates)
- {
- var dates = periodList
- .Where(p => p.StartTime != null)
- .Select(p => p.StartTime.AsUtc.ToString("yyyyMMdd'T'HHmmss'Z'"))
- .ToList();
+ var dates = sourceEvent.RecurrenceDates
+ .GetAllPeriods()
+ .Where(p => p.StartTime != null)
+ .Select(p => p.StartTime.AsUtc.ToString("yyyyMMdd'T'HHmmss'Z'"))
+ .ToList();
- if (dates.Count > 0)
- {
- recurrenceLines.Add($"RDATE:{string.Join(",", dates)}");
- }
- }
+ if (dates.Count > 0)
+ recurrenceLines.Add($"RDATE:{string.Join(",", dates)}");
}
return recurrenceLines.Count == 0