Get rid of system.drawing and uwp notifications pacakge. Remove the AOT/trim stuff for now.

This commit is contained in:
Burak Kaan Köse
2026-04-07 00:02:36 +02:00
parent ff05195416
commit f693299304
20 changed files with 422 additions and 302 deletions
+13 -14
View File
@@ -16,25 +16,24 @@
<PackageVersion Include="CommunityToolkit.WinUI.Extensions" Version="8.2.251219" />
<PackageVersion Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" Version="0.1.250926-build.2293" />
<PackageVersion Include="CommunityToolkit.WinUI.Lottie" Version="8.2.250604" />
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
<PackageVersion Include="CommunityToolkit.Labs.WinUI.DependencyPropertyGenerator" Version="0.1.250926-build.2293" />
<PackageVersion Include="EmailValidation" Version="1.3.0" />
<PackageVersion Include="gravatar-dotnet" Version="0.1.3" />
<PackageVersion Include="HtmlAgilityPack" Version="1.12.4" />
<PackageVersion Include="Ical.Net" Version="4.3.1" />
<PackageVersion Include="Ical.Net" Version="5.2.1" />
<PackageVersion Include="IsExternalInit" Version="1.0.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="5.3.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.3.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.5" />
<PackageVersion Include="Microsoft.Graph" Version="5.103.0" />
<PackageVersion Include="Microsoft.Graphics.Win2D" Version="1.4.0" />
<PackageVersion Include="Microsoft.Identity.Client" Version="4.83.3" />
<PackageVersion Include="Microsoft.Identity.Client.Broker" Version="4.82.1" />
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.82.1" />
<PackageVersion Include="Microsoft.Identity.Client.Broker" Version="4.83.3" />
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.83.3" />
<PackageVersion Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.2.14" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.1" />
<PackageVersion Include="Wino.Mail.Contracts" Version="1.0.17" />
<PackageVersion Include="Wino.Mail.Contracts" Version="1.0.18" />
<PackageVersion Include="MimeKit" Version="4.15.1" />
<PackageVersion Include="morelinq" Version="4.4.0" />
<PackageVersion Include="Nito.AsyncEx" Version="5.1.2" />
@@ -52,21 +51,21 @@
<PackageVersion Include="System.Drawing.Common" Version="10.0.5" />
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.10" />
<PackageVersion Include="System.Text.Json" Version="10.0.3" />
<PackageVersion Include="System.Text.Json" Version="10.0.5" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" />
<PackageVersion Include="H.NotifyIcon.WinUI" Version="2.4.1" />
<PackageVersion Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
<PackageVersion Include="Google.Apis.Auth" Version="1.73.0" />
<PackageVersion Include="Google.Apis.Calendar.v3" Version="1.73.0.4063" />
<PackageVersion Include="Google.Apis.Drive.v3" Version="1.73.0.4068" />
<PackageVersion Include="Google.Apis.Calendar.v3" Version="1.73.0.4073" />
<PackageVersion Include="Google.Apis.Drive.v3" Version="1.73.0.4098" />
<PackageVersion Include="Google.Apis.Gmail.v1" Version="1.73.0.4029" />
<PackageVersion Include="Google.Apis.PeopleService.v1" Version="1.72.0.3973" />
<PackageVersion Include="HtmlKit" Version="1.2.0" />
<PackageVersion Include="MailKit" Version="4.15.1" />
<PackageVersion Include="TimePeriodLibrary.NET" Version="2.1.6" />
<PackageVersion Include="System.Reactive" Version="6.1.0" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.3" />
<PackageVersion Include="System.Text.Encodings.Web" Version="10.0.3" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.5" />
<PackageVersion Include="System.Text.Encodings.Web" Version="10.0.5" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.28000.1721" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.0.250930001-experimental1" />
<PackageVersion Include="WinUIEx" Version="2.9.0" />
@@ -74,7 +73,7 @@
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageVersion Include="FluentAssertions" Version="8.8.0" />
<PackageVersion Include="FluentAssertions" Version="8.9.0" />
<PackageVersion Include="Moq" Version="4.20.72" />
</ItemGroup>
</Project>
@@ -282,6 +282,7 @@ public class CalendarPageViewModelTests
public IEnumerable<AccountCalendarViewModel> ActiveCalendars => _activeCalendars;
public IEnumerable<AccountCalendarViewModel> AllCalendars => _calendars;
public bool IsAnySynchronizationInProgress => false;
public ReadOnlyObservableGroupedCollection<MailAccount, AccountCalendarViewModel> GroupedCalendars { get; set; } = null!;
public void AddGroupedAccountCalendar(GroupedAccountCalendarViewModel groupedAccountCalendar) => _groupedCalendars.Add(groupedAccountCalendar);
+21 -12
View File
@@ -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));
}
}
+1 -1
View File
@@ -19,7 +19,7 @@
<ResourceDictionary Source="/Styles/WinoInfoBar.xaml" />
<ResourceDictionary Source="/Styles/SharedStyles.xaml" />
<ResourceDictionary Source="/Styles/IconTemplates.xaml" />
<ResourceDictionary Source="/Styles/OperationCommandBar.xaml" />
<coreStyles:OperationCommandBarResources />
<coreStyles:CustomMessageDialogStyles />
+1 -2
View File
@@ -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,
/// </summary>
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)
+21 -21
View File
@@ -34,7 +34,7 @@
<TextBlock
VerticalAlignment="Center"
Style="{StaticResource BodyStrongTextBlockStyle}"
Text="{x:Bind domain:Translator.AiActions_CheckingStatus, Mode=OneWay}" />
Text="{x:Bind domain:Translator.AiActions_CheckingStatus}" />
</StackPanel>
</StackPanel>
@@ -77,12 +77,12 @@
<StackPanel Grid.Column="1" Spacing="6">
<TextBlock
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{x:Bind domain:Translator.AiActions_SignedOutTitle, Mode=OneWay}"
Text="{x:Bind domain:Translator.AiActions_SignedOutTitle}"
TextWrapping="WrapWholeWords" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind domain:Translator.AiActions_SignedOutDescription, Mode=OneWay}"
Text="{x:Bind domain:Translator.AiActions_SignedOutDescription}"
TextWrapping="WrapWholeWords" />
</StackPanel>
</Grid>
@@ -95,12 +95,12 @@
</Grid.ColumnDefinitions>
<Button
Click="SignInButton_Click"
Content="{x:Bind domain:Translator.Buttons_SignIn, Mode=OneWay}"
Content="{x:Bind domain:Translator.Buttons_SignIn}"
Style="{StaticResource AccentButtonStyle}" />
<Button
Grid.Column="1"
Click="CreateAccountButton_Click"
Content="{x:Bind domain:Translator.Buttons_CreateAccount, Mode=OneWay}" />
Content="{x:Bind domain:Translator.Buttons_CreateAccount}" />
</Grid>
</StackPanel>
@@ -113,24 +113,24 @@
Background="{ThemeResource CardBackgroundFillColorTertiaryBrush}"
CornerRadius="12">
<StackPanel Spacing="8">
<TextBlock Style="{StaticResource BodyStrongTextBlockStyle}" Text="{x:Bind domain:Translator.AiActions_NoPackTitle, Mode=OneWay}" />
<TextBlock Style="{StaticResource BodyStrongTextBlockStyle}" Text="{x:Bind domain:Translator.AiActions_NoPackTitle}" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{x:Bind domain:Translator.AiActions_NoPackDescription, Mode=OneWay}"
Text="{x:Bind domain:Translator.AiActions_NoPackDescription}"
TextWrapping="WrapWholeWords" />
<StackPanel Orientation="Horizontal" Spacing="8">
<Border
Padding="8,2"
Background="{ThemeResource AccentFillColorDefaultBrush}"
CornerRadius="8">
<TextBlock Foreground="White" Text="{x:Bind domain:Translator.WinoAccount_Management_AiPackPromoPrice, Mode=OneWay}" />
<TextBlock Foreground="White" Text="{x:Bind domain:Translator.WinoAccount_Management_AiPackPromoPrice}" />
</Border>
<Border
Padding="8,2"
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
CornerRadius="8">
<TextBlock Text="{x:Bind domain:Translator.WinoAccount_Management_AiPackPromoRequests, Mode=OneWay}" />
<TextBlock Text="{x:Bind domain:Translator.WinoAccount_Management_AiPackPromoRequests}" />
</Border>
</StackPanel>
</StackPanel>
@@ -139,7 +139,7 @@
<Button
HorizontalAlignment="Left"
Click="PurchaseButton_Click"
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackGetButton, Mode=OneWay}"
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackGetButton}"
Style="{StaticResource AccentButtonStyle}" />
</StackPanel>
@@ -162,7 +162,7 @@
<controls:SegmentedItem
x:Name="TranslateSegment"
Padding="12,6"
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureTranslate, Mode=OneWay}">
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureTranslate}">
<controls:SegmentedItem.Icon>
<SymbolIcon Symbol="Switch" />
</controls:SegmentedItem.Icon>
@@ -170,7 +170,7 @@
<controls:SegmentedItem
x:Name="RewriteSegment"
Padding="12,6"
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureRewrite, Mode=OneWay}">
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureRewrite}">
<controls:SegmentedItem.Icon>
<SymbolIcon Symbol="Edit" />
</controls:SegmentedItem.Icon>
@@ -178,7 +178,7 @@
<controls:SegmentedItem
x:Name="SummarizeSegment"
Padding="12,6"
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureSummarize, Mode=OneWay}">
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureSummarize}">
<controls:SegmentedItem.Icon>
<SymbolIcon Symbol="Bullets" />
</controls:SegmentedItem.Icon>
@@ -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}" />
<ComboBox
x:Name="TranslateLanguageComboBox"
MinWidth="120"
@@ -216,7 +216,7 @@
<Button
x:Name="RunTranslateButton"
Click="RunTranslateButton_Click"
Content="{x:Bind domain:Translator.Composer_AiTranslateApply, Mode=OneWay}"
Content="{x:Bind domain:Translator.Composer_AiTranslateApply}"
Style="{StaticResource AccentButtonStyle}" />
</StackPanel>
@@ -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}" />
<ComboBox
x:Name="RewriteModeComboBox"
MinWidth="140"
@@ -244,7 +244,7 @@
<Button
x:Name="RunRewriteButton"
Click="RunRewriteButton_Click"
Content="{x:Bind domain:Translator.Composer_AiRewriteApply, Mode=OneWay}"
Content="{x:Bind domain:Translator.Composer_AiRewriteApply}"
Style="{StaticResource AccentButtonStyle}" />
</StackPanel>
<TextBlock
@@ -254,7 +254,7 @@
TextWrapping="WrapWholeWords" />
<TextBox
x:Name="CustomRewriteTextBox"
PlaceholderText="{x:Bind domain:Translator.Composer_AiRewriteCustomPlaceholder, Mode=OneWay}"
PlaceholderText="{x:Bind domain:Translator.Composer_AiRewriteCustomPlaceholder}"
Visibility="Collapsed" />
</StackPanel>
@@ -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}" />
<ComboBox
x:Name="SummarizeLanguageComboBox"
MinWidth="120"
@@ -285,7 +285,7 @@
x:Name="RunSummarizeButton"
HorizontalAlignment="Left"
Click="RunSummarizeButton_Click"
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureSummarize, Mode=OneWay}"
Content="{x:Bind domain:Translator.WinoAccount_Management_AiPackFeatureSummarize}"
Style="{StaticResource AccentButtonStyle}" />
<FontIcon
x:Name="SummarizeCachedIndicator"
@@ -295,7 +295,7 @@
FontSize="16"
Foreground="#2AA84A"
Glyph="&#xE73E;"
ToolTipService.ToolTip="{x:Bind domain:Translator.Composer_AiSummarize, Mode=OneWay}"
ToolTipService.ToolTip="{x:Bind domain:Translator.Composer_AiSummarize}"
Visibility="Collapsed" />
</StackPanel>
+18 -18
View File
@@ -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,9 +430,12 @@ public sealed partial class OperationCommandBar : CommandBar
: CommandBarOverflowButtonVisibility.Auto;
}
private sealed class MenuOperationCommandBarItemViewModel
{
public MenuOperationCommandBarItemViewModel(IMenuOperation operation, string label, WinoIconGlyph icon, CommandBarLabelPosition labelPosition)
private sealed class SeparatorCommandBarItemViewModel;
}
public sealed class OperationCommandBarMenuOperationItemViewModel
{
public OperationCommandBarMenuOperationItemViewModel(IMenuOperation operation, string label, WinoIconGlyph icon, CommandBarLabelPosition labelPosition)
{
Operation = operation;
Label = label;
@@ -447,11 +450,11 @@ public sealed partial class OperationCommandBar : CommandBar
public string ToolTip { get; }
public bool IsEnabled => Operation.IsEnabled;
public CommandBarLabelPosition LabelPosition { get; }
}
}
private sealed class AIActionsCommandBarItemViewModel
{
public AIActionsCommandBarItemViewModel(string toolTip, string glyph)
public sealed class OperationCommandBarAIActionsItemViewModel
{
public OperationCommandBarAIActionsItemViewModel(string toolTip, string glyph)
{
ToolTip = toolTip;
Glyph = glyph;
@@ -459,11 +462,11 @@ public sealed partial class OperationCommandBar : CommandBar
public string ToolTip { get; }
public string Glyph { get; }
}
}
private sealed class ThemeCommandBarItemViewModel
{
public ThemeCommandBarItemViewModel(string toolTip, WinoIconGlyph icon)
public sealed class OperationCommandBarThemeItemViewModel
{
public OperationCommandBarThemeItemViewModel(string toolTip, WinoIconGlyph icon)
{
ToolTip = toolTip;
Icon = icon;
@@ -471,7 +474,4 @@ public sealed partial class OperationCommandBar : CommandBar
public string ToolTip { get; }
public WinoIconGlyph Icon { get; }
}
private sealed class SeparatorCommandBarItemViewModel;
}
+2 -10
View File
@@ -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<string>();
// 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);
@@ -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)
@@ -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<string> 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<string>(printerCount);
var structSize = Marshal.SizeOf<PrinterInfo4>();
for (var i = 0; i < printerCount; i++)
{
var current = IntPtr.Add(printerBuffer, i * structSize);
var info = Marshal.PtrToStructure<PrinterInfo4>(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);
}
}
+55
View File
@@ -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<string, string>(StringComparer.Ordinal));
private readonly IReadOnlyDictionary<string, string> _values;
private NotificationArguments(IReadOnlyDictionary<string, string> 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<string, string>(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<TEnum>(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);
}
}
+1 -1
View File
@@ -23,7 +23,7 @@
<Identity
Name="58272BurakKSE.WinoMailPreview"
Publisher="CN=51FBDAF3-E212-4149-89A2-A2636B3BC911"
Version="2.0.15.0" />
Version="2.0.0.0" />
<mp:PhoneIdentity PhoneProductId="3879fcfb-a561-4599-9103-e0c9b35a271f" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
+1 -2
View File
@@ -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)
+67 -109
View File
@@ -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,27 +194,25 @@ 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)
{
Log.Error(ex, "Error while updating taskbar badge.");
@@ -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");
}
}
+19 -18
View File
@@ -1,4 +1,5 @@
<ResourceDictionary
x:Class="Wino.Mail.WinUI.Styles.OperationCommandBarResources"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Wino.Mail.WinUI.Controls">
@@ -10,50 +11,50 @@
<Setter Property="OverflowButtonVisibility" Value="Auto" />
</Style>
<DataTemplate x:Key="OperationCommandBarMailOperationTemplate">
<DataTemplate x:Key="OperationCommandBarMailOperationTemplate" x:DataType="controls:OperationCommandBarMenuOperationItemViewModel">
<AppBarButton
MinWidth="40"
IsEnabled="{Binding IsEnabled}"
Label="{Binding Label}"
LabelPosition="{Binding LabelPosition}"
ToolTipService.ToolTip="{Binding ToolTip}">
IsEnabled="{x:Bind IsEnabled}"
Label="{x:Bind Label}"
LabelPosition="{x:Bind LabelPosition}"
ToolTipService.ToolTip="{x:Bind ToolTip}">
<AppBarButton.Icon>
<controls:WinoFontIcon Icon="{Binding Icon}" />
<controls:WinoFontIcon Icon="{x:Bind Icon}" />
</AppBarButton.Icon>
</AppBarButton>
</DataTemplate>
<DataTemplate x:Key="OperationCommandBarFolderOperationTemplate">
<DataTemplate x:Key="OperationCommandBarFolderOperationTemplate" x:DataType="controls:OperationCommandBarMenuOperationItemViewModel">
<AppBarButton
MinWidth="40"
IsEnabled="{Binding IsEnabled}"
Label="{Binding Label}"
LabelPosition="{Binding LabelPosition}"
ToolTipService.ToolTip="{Binding ToolTip}">
IsEnabled="{x:Bind IsEnabled}"
Label="{x:Bind Label}"
LabelPosition="{x:Bind LabelPosition}"
ToolTipService.ToolTip="{x:Bind ToolTip}">
<AppBarButton.Icon>
<controls:WinoFontIcon Icon="{Binding Icon}" />
<controls:WinoFontIcon Icon="{x:Bind Icon}" />
</AppBarButton.Icon>
</AppBarButton>
</DataTemplate>
<DataTemplate x:Key="OperationCommandBarAIActionsTemplate">
<DataTemplate x:Key="OperationCommandBarAIActionsTemplate" x:DataType="controls:OperationCommandBarAIActionsItemViewModel">
<AppBarToggleButton
MinWidth="40"
LabelPosition="Collapsed"
ToolTipService.ToolTip="{Binding ToolTip}">
ToolTipService.ToolTip="{x:Bind ToolTip}">
<AppBarToggleButton.Icon>
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="{Binding Glyph}" />
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="{x:Bind Glyph}" />
</AppBarToggleButton.Icon>
</AppBarToggleButton>
</DataTemplate>
<DataTemplate x:Key="OperationCommandBarThemeToggleTemplate">
<DataTemplate x:Key="OperationCommandBarThemeToggleTemplate" x:DataType="controls:OperationCommandBarThemeItemViewModel">
<AppBarButton
MinWidth="40"
LabelPosition="Collapsed"
ToolTipService.ToolTip="{Binding ToolTip}">
ToolTipService.ToolTip="{x:Bind ToolTip}">
<AppBarButton.Icon>
<controls:WinoFontIcon Icon="{Binding Icon}" />
<controls:WinoFontIcon Icon="{x:Bind Icon}" />
</AppBarButton.Icon>
</AppBarButton>
</DataTemplate>
@@ -0,0 +1,11 @@
using Microsoft.UI.Xaml;
namespace Wino.Mail.WinUI.Styles;
public partial class OperationCommandBarResources : ResourceDictionary
{
public OperationCommandBarResources()
{
InitializeComponent();
}
}
+3 -3
View File
@@ -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}">
<StackPanel Orientation="Horizontal" Spacing="6">
<PathIcon x:Name="AttentionIcon" Data="F1 M 2.021484 18.769531 C 1.767578 18.769531 1.52832 18.720703 1.303711 18.623047 C 1.079102 18.525391 0.880534 18.391928 0.708008 18.222656 C 0.535482 18.053385 0.398763 17.856445 0.297852 17.631836 C 0.19694 17.407227 0.146484 17.167969 0.146484 16.914062 C 0.146484 16.614584 0.211589 16.328125 0.341797 16.054688 L 7.695312 1.347656 C 7.851562 1.035156 8.082682 0.784506 8.388672 0.595703 C 8.694661 0.406902 9.023438 0.3125 9.375 0.3125 C 9.726562 0.3125 10.055338 0.406902 10.361328 0.595703 C 10.667317 0.784506 10.898438 1.035156 11.054688 1.347656 L 18.408203 16.054688 C 18.53841 16.328125 18.603516 16.614584 18.603516 16.914062 C 18.603516 17.167969 18.553059 17.407227 18.452148 17.631836 C 18.351236 17.856445 18.216145 18.053385 18.046875 18.222656 C 17.877604 18.391928 17.679035 18.525391 17.451172 18.623047 C 17.223307 18.720703 16.982422 18.769531 16.728516 18.769531 Z M 16.728516 17.519531 C 16.884766 17.519531 17.027994 17.460938 17.158203 17.34375 C 17.28841 17.226562 17.353516 17.086588 17.353516 16.923828 C 17.353516 16.806641 17.330729 16.702475 17.285156 16.611328 L 9.931641 1.904297 C 9.879557 1.793621 9.80306 1.708984 9.702148 1.650391 C 9.601236 1.591797 9.492188 1.5625 9.375 1.5625 C 9.257812 1.5625 9.148763 1.593426 9.047852 1.655273 C 8.946939 1.717123 8.870442 1.800131 8.818359 1.904297 L 1.464844 16.611328 C 1.419271 16.702475 1.396484 16.803387 1.396484 16.914062 C 1.396484 17.083334 1.459961 17.226562 1.586914 17.34375 C 1.713867 17.460938 1.858724 17.519531 2.021484 17.519531 Z M 8.75 11.875 L 8.75 6.875 C 8.75 6.705729 8.811849 6.559245 8.935547 6.435547 C 9.059244 6.31185 9.205729 6.25 9.375 6.25 C 9.544271 6.25 9.690755 6.31185 9.814453 6.435547 C 9.93815 6.559245 10 6.705729 10 6.875 L 10 11.875 C 10 12.044271 9.93815 12.190756 9.814453 12.314453 C 9.690755 12.438151 9.544271 12.5 9.375 12.5 C 9.205729 12.5 9.059244 12.438151 8.935547 12.314453 C 8.811849 12.190756 8.75 12.044271 8.75 11.875 Z M 8.4375 14.375 C 8.4375 14.114584 8.528646 13.893229 8.710938 13.710938 C 8.893229 13.528646 9.114583 13.4375 9.375 13.4375 C 9.635416 13.4375 9.856771 13.528646 10.039062 13.710938 C 10.221354 13.893229 10.3125 14.114584 10.3125 14.375 C 10.3125 14.635417 10.221354 14.856771 10.039062 15.039062 C 9.856771 15.221354 9.635416 15.3125 9.375 15.3125 C 9.114583 15.3125 8.893229 15.221354 8.710938 15.039062 C 8.528646 14.856771 8.4375 14.635417 8.4375 14.375 Z " />
<TextBlock Text="{x:Bind domain:Translator.Info_AccountAttentionRequiredAction, Mode=OneWay}" />
<TextBlock Text="{x:Bind domain:Translator.Info_AccountAttentionRequiredAction}" />
</StackPanel>
</Button>
@@ -567,8 +567,8 @@
<ProgressBar
Height="4"
IsIndeterminate="True"
ShowPaused="False"
ShowError="False"
ShowPaused="False"
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}" />
</StackPanel>
</Grid>
-21
View File
@@ -13,29 +13,10 @@
<EnableMsixTooling>true</EnableMsixTooling>
<Nullable>enable</Nullable>
<!-- AOT / Trimming -->
<PublishAot Condition="'$(Configuration)' == 'Debug'">False</PublishAot>
<PublishAot Condition="'$(Configuration)' != 'Debug'">True</PublishAot>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<NoWarn>$(NoWarn);CS8305</NoWarn>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
<!-- Trimming -->
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
<!-- Single instancing -->
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>
</PropertyGroup>
<ItemGroup>
<TrimmerRootAssembly Include="Google.Apis.Auth" />
<TrimmerRootAssembly Include="Google.Apis.Drive.v3" />
<TrimmerRootAssembly Include="Google.Apis.Gmail.v1" />
</ItemGroup>
<ItemGroup>
<Content Remove="Assets\BadgeLogo.scale-100.png" />
@@ -203,7 +184,6 @@
<PackageReference Include="CommunityToolkit.WinUI.Lottie" />
<PackageReference Include="Microsoft.Graphics.Win2D" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" />
@@ -215,7 +195,6 @@
<PackageReference Include="Sentry.Serilog" />
<PackageReference Include="sqlite-net-pcl" />
<PackageReference Include="EmailValidation" />
<PackageReference Include="System.Drawing.Common" />
<PackageReference Include="SkiaSharp.Views.WinUI" />
<PackageReference Include="WinUIEx" />
<PackageReference Include="Wino.Mail.Contracts" />
+30 -4
View File
@@ -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<Guid, int> accountIdOrderPair)
{
foreach (var pair in accountIdOrderPair)
+42 -27
View File
@@ -421,14 +421,14 @@ public sealed class CalDavClient : ICalDavClient
var result = new List<CalDavCalendarEvent>();
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<string>(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,35 +687,27 @@ 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'"))
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 (sourceEvent.RecurrenceDates != null)
{
foreach (var periodList in sourceEvent.RecurrenceDates)
{
var dates = periodList
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)}");
}
}
}
return recurrenceLines.Count == 0
? string.Empty