Filter reminder snooze options by default reminder

This commit is contained in:
Burak Kaan Köse
2026-02-27 21:57:41 +01:00
parent 0e742c7a8f
commit c942066878
11 changed files with 223 additions and 33 deletions
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Wino.Core.Domain;
public static class CalendarReminderSnoozeOptions
{
private static readonly int[] SupportedSnoozeMinutes = [5, 10, 15, 30];
public static IReadOnlyList<int> GetAllowedSnoozeMinutes(long reminderDurationInSeconds, long defaultReminderDurationInSeconds)
{
var reminderMinutes = (int)Math.Max(0, reminderDurationInSeconds / 60);
if (reminderMinutes <= 0)
return [];
var maxSnoozeMinutes = reminderMinutes;
var defaultReminderMinutes = (int)Math.Max(0, defaultReminderDurationInSeconds / 60);
if (defaultReminderMinutes > 0)
maxSnoozeMinutes = Math.Min(maxSnoozeMinutes, defaultReminderMinutes);
return SupportedSnoozeMinutes.Where(minutes => minutes <= maxSnoozeMinutes).ToArray();
}
}
+2
View File
@@ -16,6 +16,8 @@ public static class Constants
public const string ToastCalendarItemIdKey = nameof(ToastCalendarItemIdKey); public const string ToastCalendarItemIdKey = nameof(ToastCalendarItemIdKey);
public const string ToastCalendarActionKey = nameof(ToastCalendarActionKey); public const string ToastCalendarActionKey = nameof(ToastCalendarActionKey);
public const string ToastCalendarNavigateAction = nameof(ToastCalendarNavigateAction); public const string ToastCalendarNavigateAction = nameof(ToastCalendarNavigateAction);
public const string ToastCalendarSnoozeAction = nameof(ToastCalendarSnoozeAction);
public const string ToastCalendarSnoozeDurationInputId = nameof(ToastCalendarSnoozeDurationInputId);
public const string ToastModeKey = nameof(ToastModeKey); public const string ToastModeKey = nameof(ToastModeKey);
public const string ToastModeMail = nameof(ToastModeMail); public const string ToastModeMail = nameof(ToastModeMail);
public const string ToastModeCalendar = nameof(ToastModeCalendar); public const string ToastModeCalendar = nameof(ToastModeCalendar);
@@ -138,6 +138,7 @@ public class CalendarItem : ICalendarItem
// TODO // TODO
public string CustomEventColorHex { get; set; } public string CustomEventColorHex { get; set; }
public string HtmlLink { get; set; } public string HtmlLink { get; set; }
public DateTime? SnoozedUntil { get; set; }
public CalendarItemStatus Status { get; set; } public CalendarItemStatus Status { get; set; }
public CalendarItemVisibility Visibility { get; set; } public CalendarItemVisibility Visibility { get; set; }
@@ -42,6 +42,7 @@ public interface ICalendarService
Task UpdateCalendarItemAsync(CalendarItem calendarItem, List<CalendarEventAttendee> attendees); Task UpdateCalendarItemAsync(CalendarItem calendarItem, List<CalendarEventAttendee> attendees);
Task<List<Reminder>> GetRemindersAsync(Guid calendarItemId); Task<List<Reminder>> GetRemindersAsync(Guid calendarItemId);
Task SaveRemindersAsync(Guid calendarItemId, List<Reminder> reminders); Task SaveRemindersAsync(Guid calendarItemId, List<Reminder> reminders);
Task SnoozeCalendarItemAsync(Guid calendarItemId, DateTime snoozedUntilLocal);
/// <summary> /// <summary>
/// Checks due reminder windows and returns reminder notifications that should trigger now. /// Checks due reminder windows and returns reminder notifications that should trigger now.
@@ -133,6 +133,7 @@
"CalendarEventDetails_People": "People", "CalendarEventDetails_People": "People",
"CalendarEventDetails_ReadOnlyEvent": "Read-only event", "CalendarEventDetails_ReadOnlyEvent": "Read-only event",
"CalendarEventDetails_Reminder": "Reminder", "CalendarEventDetails_Reminder": "Reminder",
"CalendarReminder_SnoozeMinutesOption": "{0} minutes",
"CalendarEventDetails_ShowAs": "Show as", "CalendarEventDetails_ShowAs": "Show as",
"CalendarShowAs_Free": "Free", "CalendarShowAs_Free": "Free",
"CalendarShowAs_Tentative": "Tentative", "CalendarShowAs_Tentative": "Tentative",
@@ -0,0 +1,48 @@
using FluentAssertions;
using Wino.Core.Domain;
using Xunit;
namespace Wino.Core.Tests;
public class CalendarReminderSnoozeOptionsTests
{
[Fact]
public void GetAllowedSnoozeMinutes_WhenDefaultIs15AndReminderIs15_Excludes30()
{
var options = CalendarReminderSnoozeOptions.GetAllowedSnoozeMinutes(
reminderDurationInSeconds: 15 * 60,
defaultReminderDurationInSeconds: 15 * 60);
options.Should().Equal(5, 10, 15);
}
[Fact]
public void GetAllowedSnoozeMinutes_WhenReminderIs5AndDefaultIs15_DoesNotPassEventStart()
{
var options = CalendarReminderSnoozeOptions.GetAllowedSnoozeMinutes(
reminderDurationInSeconds: 5 * 60,
defaultReminderDurationInSeconds: 15 * 60);
options.Should().Equal(5);
}
[Fact]
public void GetAllowedSnoozeMinutes_WhenDefaultReminderIsNone_UsesReminderDurationOnly()
{
var options = CalendarReminderSnoozeOptions.GetAllowedSnoozeMinutes(
reminderDurationInSeconds: 30 * 60,
defaultReminderDurationInSeconds: 0);
options.Should().Equal(5, 10, 15, 30);
}
[Fact]
public void GetAllowedSnoozeMinutes_WhenReminderIsUnderFiveMinutes_ReturnsNoOptions()
{
var options = CalendarReminderSnoozeOptions.GetAllowedSnoozeMinutes(
reminderDurationInSeconds: 60,
defaultReminderDurationInSeconds: 15 * 60);
options.Should().BeEmpty();
}
}
@@ -68,8 +68,8 @@ public class CalendarReminderServiceTests : IAsyncLifetime
due.Should().HaveCount(1); due.Should().HaveCount(1);
due[0].CalendarItem.Id.Should().Be(calendarItem.Id); due[0].CalendarItem.Id.Should().Be(calendarItem.Id);
due[0].ReminderDurationInSeconds.Should().Be(5 * 60); due[0].ReminderDurationInSeconds.Should().Be(5 * 60);
due[0].ReminderKey.Should().Be($"{calendarItem.Id:N}:{5 * 60}"); due[0].ReminderKey.Should().StartWith($"{calendarItem.Id:N}:{5 * 60}:");
sentReminderKeys.Should().Contain($"{calendarItem.Id:N}:{5 * 60}"); sentReminderKeys.Should().ContainSingle(k => k.StartsWith($"{calendarItem.Id:N}:{5 * 60}:"));
} }
[Fact] [Fact]
@@ -108,7 +108,7 @@ public class CalendarReminderServiceTests : IAsyncLifetime
firstRun.Should().HaveCount(1); firstRun.Should().HaveCount(1);
secondRun.Should().BeEmpty(); secondRun.Should().BeEmpty();
sentReminderKeys.Should().Contain($"{calendarItem.Id:N}:{5 * 60}"); sentReminderKeys.Should().ContainSingle(k => k.StartsWith($"{calendarItem.Id:N}:{5 * 60}:"));
} }
[Fact] [Fact]
@@ -189,6 +189,35 @@ public class CalendarReminderServiceTests : IAsyncLifetime
due.Should().BeEmpty(); due.Should().BeEmpty();
} }
[Fact]
public async Task CheckAndNotifyAsync_WhenItemIsSnoozed_TriggersAtSnoozedTime()
{
var nowLocal = new DateTime(2026, 1, 1, 10, 0, 0);
var lastCheckLocal = nowLocal.AddSeconds(-30);
var calendarItem = await CreateCalendarItemWithReminderAsync(
startDate: nowLocal.AddMinutes(5),
reminderDurationInSeconds: 5 * 60,
reminderType: CalendarItemReminderType.Popup);
await _calendarService.SnoozeCalendarItemAsync(calendarItem.Id, nowLocal.AddMinutes(10));
HashSet<string> sentReminderKeys = [];
var dueAtOriginalTrigger = await _calendarService.CheckAndNotifyAsync(lastCheckLocal, nowLocal, sentReminderKeys);
dueAtOriginalTrigger.Should().BeEmpty();
var snoozeTriggerWindowStart = nowLocal.AddMinutes(10).AddSeconds(-30);
var snoozeTriggerWindowEnd = nowLocal.AddMinutes(10);
var dueAtSnoozeTime = await _calendarService.CheckAndNotifyAsync(snoozeTriggerWindowStart, snoozeTriggerWindowEnd, sentReminderKeys);
dueAtSnoozeTime.Should().HaveCount(1);
dueAtSnoozeTime[0].CalendarItem.Id.Should().Be(calendarItem.Id);
dueAtSnoozeTime[0].ReminderKey.Should().StartWith($"{calendarItem.Id:N}:{5 * 60}:");
}
private async Task<CalendarItem> CreateCalendarItemWithReminderAsync( private async Task<CalendarItem> CreateCalendarItemWithReminderAsync(
DateTime startDate, DateTime startDate,
long reminderDurationInSeconds, long reminderDurationInSeconds,
+38 -3
View File
@@ -215,12 +215,20 @@ public partial class App : WinoApplication,
// Check calendar reminder toast activation first. // Check calendar reminder toast activation first.
if (toastArguments.TryGetValue(Constants.ToastCalendarActionKey, out string calendarAction) && if (toastArguments.TryGetValue(Constants.ToastCalendarActionKey, out string calendarAction) &&
calendarAction == Constants.ToastCalendarNavigateAction &&
toastArguments.TryGetValue(Constants.ToastCalendarItemIdKey, out string calendarItemIdString) && toastArguments.TryGetValue(Constants.ToastCalendarItemIdKey, out string calendarItemIdString) &&
Guid.TryParse(calendarItemIdString, out Guid calendarItemId)) Guid.TryParse(calendarItemIdString, out Guid calendarItemId))
{ {
await HandleCalendarToastNavigationAsync(calendarItemId); if (calendarAction == Constants.ToastCalendarNavigateAction)
return; {
await HandleCalendarToastNavigationAsync(calendarItemId);
return;
}
if (calendarAction == Constants.ToastCalendarSnoozeAction)
{
await HandleCalendarToastSnoozeAsync(toastArgs, calendarItemId);
return;
}
} }
// Check if this is a navigation toast (user clicked the notification). // Check if this is a navigation toast (user clicked the notification).
@@ -267,6 +275,33 @@ public partial class App : WinoApplication,
navigationService.Navigate(WinoPage.EventDetailsPage, target); navigationService.Navigate(WinoPage.EventDetailsPage, target);
} }
private async Task HandleCalendarToastSnoozeAsync(AppNotificationActivatedEventArgs toastArgs, Guid calendarItemId)
{
if (!TryGetSnoozeDurationMinutes(toastArgs, out var snoozeDurationMinutes))
return;
var calendarService = Services.GetRequiredService<ICalendarService>();
var snoozedUntilLocal = DateTime.Now.AddMinutes(snoozeDurationMinutes);
await calendarService.SnoozeCalendarItemAsync(calendarItemId, snoozedUntilLocal).ConfigureAwait(false);
}
private static bool TryGetSnoozeDurationMinutes(AppNotificationActivatedEventArgs toastArgs, out int snoozeDurationMinutes)
{
snoozeDurationMinutes = 0;
if (toastArgs.UserInput == null ||
!toastArgs.UserInput.TryGetValue(Constants.ToastCalendarSnoozeDurationInputId, out var selectedValue) ||
selectedValue == null)
{
return false;
}
var selectedText = selectedValue.ToString();
return int.TryParse(selectedText, out snoozeDurationMinutes) && snoozeDurationMinutes > 0;
}
/// <summary> /// <summary>
/// Handles toast notification click for navigation. /// Handles toast notification click for navigation.
/// Creates window if not running, sets up navigation parameter. /// Creates window if not running, sets up navigation parameter.
+48 -24
View File
@@ -5,7 +5,6 @@ using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Toolkit.Uwp.Notifications; using Microsoft.Toolkit.Uwp.Notifications;
using Serilog; using Serilog;
using Windows.ApplicationModel;
using Windows.Data.Xml.Dom; using Windows.Data.Xml.Dom;
using Windows.UI.Notifications; using Windows.UI.Notifications;
using Wino.Core.Domain.Entities.Calendar; using Wino.Core.Domain.Entities.Calendar;
@@ -26,16 +25,19 @@ public class NotificationBuilder : INotificationBuilder
private readonly IFolderService _folderService; private readonly IFolderService _folderService;
private readonly IMailService _mailService; private readonly IMailService _mailService;
private readonly IThumbnailService _thumbnailService; private readonly IThumbnailService _thumbnailService;
private readonly IPreferencesService _preferencesService;
public NotificationBuilder(IAccountService accountService, public NotificationBuilder(IAccountService accountService,
IFolderService folderService, IFolderService folderService,
IMailService mailService, IMailService mailService,
IThumbnailService thumbnailService) IThumbnailService thumbnailService,
IPreferencesService preferencesService)
{ {
_accountService = accountService; _accountService = accountService;
_folderService = folderService; _folderService = folderService;
_mailService = mailService; _mailService = mailService;
_thumbnailService = thumbnailService; _thumbnailService = thumbnailService;
_preferencesService = preferencesService;
WeakReferenceMessenger.Default.Register<MailReadStatusChanged>(this, (r, msg) => WeakReferenceMessenger.Default.Register<MailReadStatusChanged>(this, (r, msg) =>
{ {
@@ -96,7 +98,7 @@ public class NotificationBuilder : INotificationBuilder
Src = new Uri("ms-winsoundevent:Notification.Mail") Src = new Uri("ms-winsoundevent:Notification.Mail")
}); });
ShowToast(builder, ToastTargetApp.Mail); ShowToast(builder);
} }
else else
{ {
@@ -151,7 +153,7 @@ public class NotificationBuilder : INotificationBuilder
}); });
// Use UniqueId as tag to allow removal // Use UniqueId as tag to allow removal
ShowToast(builder, ToastTargetApp.Mail, mailItem.UniqueId.ToString()); ShowToast(builder, mailItem.UniqueId.ToString());
} }
private ToastButton GetDismissButton() private ToastButton GetDismissButton()
@@ -242,7 +244,7 @@ public class NotificationBuilder : INotificationBuilder
{ {
try try
{ {
ToastNotificationManager.History.Remove(mailUniqueId.ToString(), null, GetAppUserModelId(ToastTargetApp.Mail)); ToastNotificationManager.History.Remove(mailUniqueId.ToString());
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -263,7 +265,7 @@ public class NotificationBuilder : INotificationBuilder
builder.AddArgument(Constants.ToastMailAccountIdKey, account.Id.ToString()); builder.AddArgument(Constants.ToastMailAccountIdKey, account.Id.ToString());
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail); builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
builder.AddButton(new ToastButton().SetContent(Translator.Buttons_FixAccount)); builder.AddButton(new ToastButton().SetContent(Translator.Buttons_FixAccount));
ShowToast(builder, ToastTargetApp.Mail); ShowToast(builder);
} }
public void CreateWebView2RuntimeMissingNotification() public void CreateWebView2RuntimeMissingNotification()
@@ -276,7 +278,7 @@ public class NotificationBuilder : INotificationBuilder
builder.AddButton(GetDismissButton()); builder.AddButton(GetDismissButton());
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail); builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
ShowToast(builder, ToastTargetApp.Mail); ShowToast(builder);
} }
public Task CreateCalendarReminderNotificationAsync(CalendarItem calendarItem, long reminderDurationInSeconds) public Task CreateCalendarReminderNotificationAsync(CalendarItem calendarItem, long reminderDurationInSeconds)
@@ -302,19 +304,54 @@ public class NotificationBuilder : INotificationBuilder
builder.AddArgument(Constants.ToastCalendarActionKey, Constants.ToastCalendarNavigateAction); builder.AddArgument(Constants.ToastCalendarActionKey, Constants.ToastCalendarNavigateAction);
builder.AddArgument(Constants.ToastCalendarItemIdKey, calendarItem.Id.ToString()); builder.AddArgument(Constants.ToastCalendarItemIdKey, calendarItem.Id.ToString());
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar); builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar);
builder.AddButton(GetDismissButton());
var allowedSnoozeMinutes = CalendarReminderSnoozeOptions.GetAllowedSnoozeMinutes(
reminderDurationInSeconds,
_preferencesService.DefaultReminderDurationInSeconds);
if (allowedSnoozeMinutes.Count > 0)
{
var selectionBox = new ToastSelectionBox(Constants.ToastCalendarSnoozeDurationInputId)
{
DefaultSelectionBoxItemId = allowedSnoozeMinutes[0].ToString()
};
foreach (var snoozeMinutes in allowedSnoozeMinutes)
{
selectionBox.Items.Add(new ToastSelectionBoxItem(
snoozeMinutes.ToString(),
string.Format(Translator.CalendarReminder_SnoozeMinutesOption, snoozeMinutes)));
}
builder.AddInput(selectionBox);
builder.AddButton(new ToastButtonSnooze()
.SetSelectionBoxId(Constants.ToastCalendarSnoozeDurationInputId)
.AddArgument(Constants.ToastCalendarActionKey, Constants.ToastCalendarSnoozeAction)
.AddArgument(Constants.ToastCalendarItemIdKey, calendarItem.Id.ToString())
.AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar));
}
builder.AddButton(new ToastButtonDismiss());
if (Uri.TryCreate(calendarItem.HtmlLink, UriKind.Absolute, out var joinUri))
{
builder.AddButton(new ToastButton()
.SetContent(Translator.CalendarEventDetails_JoinOnline)
.SetProtocolActivation(joinUri));
}
builder.AddAudio(new ToastAudio() builder.AddAudio(new ToastAudio()
{ {
Src = new Uri("ms-winsoundevent:Notification.Reminder") Src = new Uri("ms-winsoundevent:Notification.Reminder")
}); });
var tag = $"calendar-reminder-{calendarItem.Id:N}-{reminderDurationInSeconds}"; var tag = $"calendar-reminder-{calendarItem.Id:N}-{reminderDurationInSeconds}";
ShowToast(builder, ToastTargetApp.Calendar, tag); ShowToast(builder, tag);
return Task.CompletedTask; return Task.CompletedTask;
} }
private static void ShowToast(ToastContentBuilder builder, ToastTargetApp targetApp, string? tag = null) private static void ShowToast(ToastContentBuilder builder, string? tag = null)
{ {
var toastNotification = new ToastNotification(builder.GetToastContent().GetXml()); var toastNotification = new ToastNotification(builder.GetToastContent().GetXml());
@@ -323,20 +360,7 @@ public class NotificationBuilder : INotificationBuilder
toastNotification.Tag = tag; toastNotification.Tag = tag;
} }
var appUserModelId = GetAppUserModelId(targetApp); var notifier = ToastNotificationManager.CreateToastNotifier();
var notifier = ToastNotificationManager.CreateToastNotifier(appUserModelId);
notifier.Show(toastNotification); notifier.Show(toastNotification);
} }
private static string GetAppUserModelId(ToastTargetApp targetApp)
{
_ = targetApp;
return $"{Package.Current.Id.FamilyName}!{MailApplicationId}";
}
private enum ToastTargetApp
{
Mail,
Calendar
}
} }
+17 -3
View File
@@ -336,6 +336,12 @@ public class CalendarService : BaseDatabaseService, ICalendarService
}); });
} }
public Task SnoozeCalendarItemAsync(Guid calendarItemId, DateTime snoozedUntilLocal)
=> Connection.ExecuteAsync(
$"UPDATE {nameof(CalendarItem)} SET {nameof(CalendarItem.SnoozedUntil)} = ? WHERE {nameof(CalendarItem.Id)} = ?",
snoozedUntilLocal,
calendarItemId);
public async Task<List<CalendarReminderNotificationRequest>> CheckAndNotifyAsync(DateTime lastCheckLocal, DateTime nowLocal, ISet<string> sentReminderKeys, CancellationToken cancellationToken = default) public async Task<List<CalendarReminderNotificationRequest>> CheckAndNotifyAsync(DateTime lastCheckLocal, DateTime nowLocal, ISet<string> sentReminderKeys, CancellationToken cancellationToken = default)
{ {
if (sentReminderKeys == null) if (sentReminderKeys == null)
@@ -347,7 +353,8 @@ public class CalendarService : BaseDatabaseService, ICalendarService
c.Id AS CalendarItemId, c.Id AS CalendarItemId,
c.StartDate, c.StartDate,
c.StartTimeZone, c.StartTimeZone,
r.DurationInSeconds AS ReminderDurationInSeconds r.DurationInSeconds AS ReminderDurationInSeconds,
c.SnoozedUntil
FROM CalendarItem c FROM CalendarItem c
INNER JOIN Reminder r ON r.CalendarItemId = c.Id INNER JOIN Reminder r ON r.CalendarItemId = c.Id
INNER JOIN AccountCalendar ac ON ac.Id = c.CalendarId INNER JOIN AccountCalendar ac ON ac.Id = c.CalendarId
@@ -367,11 +374,14 @@ public class CalendarService : BaseDatabaseService, ICalendarService
var eventStartLocal = candidate.StartDate.ToLocalTimeFromTimeZone(candidate.StartTimeZone); var eventStartLocal = candidate.StartDate.ToLocalTimeFromTimeZone(candidate.StartTimeZone);
var triggerTimeLocal = eventStartLocal.AddSeconds(-candidate.ReminderDurationInSeconds); var triggerTimeLocal = eventStartLocal.AddSeconds(-candidate.ReminderDurationInSeconds);
var effectiveTriggerTimeLocal = candidate.SnoozedUntil.HasValue
? MaxDateTime(triggerTimeLocal, candidate.SnoozedUntil.Value)
: triggerTimeLocal;
if (triggerTimeLocal <= lastCheckLocal || triggerTimeLocal > nowLocal) if (effectiveTriggerTimeLocal <= lastCheckLocal || effectiveTriggerTimeLocal > nowLocal)
continue; continue;
var reminderKey = $"{candidate.CalendarItemId:N}:{candidate.ReminderDurationInSeconds}"; var reminderKey = $"{candidate.CalendarItemId:N}:{candidate.ReminderDurationInSeconds}:{effectiveTriggerTimeLocal.Ticks}";
if (!sentReminderKeys.Add(reminderKey)) if (!sentReminderKeys.Add(reminderKey))
continue; continue;
@@ -438,11 +448,15 @@ public class CalendarService : BaseDatabaseService, ICalendarService
#endregion #endregion
private static DateTime MaxDateTime(DateTime first, DateTime second)
=> first >= second ? first : second;
private sealed class CalendarReminderCandidate private sealed class CalendarReminderCandidate
{ {
public Guid CalendarItemId { get; set; } public Guid CalendarItemId { get; set; }
public DateTime StartDate { get; set; } public DateTime StartDate { get; set; }
public string StartTimeZone { get; set; } public string StartTimeZone { get; set; }
public long ReminderDurationInSeconds { get; set; } public long ReminderDurationInSeconds { get; set; }
public DateTime? SnoozedUntil { get; set; }
} }
} }
+9
View File
@@ -117,6 +117,15 @@ public class DatabaseService : IDatabaseService
.ExecuteAsync($"ALTER TABLE {nameof(CustomServerInformation)} ADD COLUMN {nameof(CustomServerInformation.CalendarSupportMode)} INTEGER NOT NULL DEFAULT 0") .ExecuteAsync($"ALTER TABLE {nameof(CustomServerInformation)} ADD COLUMN {nameof(CustomServerInformation.CalendarSupportMode)} INTEGER NOT NULL DEFAULT 0")
.ConfigureAwait(false); .ConfigureAwait(false);
} }
var calendarItemColumns = await Connection.GetTableInfoAsync(nameof(CalendarItem)).ConfigureAwait(false);
if (!calendarItemColumns.Any(c => c.Name == nameof(CalendarItem.SnoozedUntil)))
{
await Connection
.ExecuteAsync($"ALTER TABLE {nameof(CalendarItem)} ADD COLUMN {nameof(CalendarItem.SnoozedUntil)} TEXT NULL")
.ConfigureAwait(false);
}
} }
private async Task EnsureIndexesAsync() private async Task EnsureIndexesAsync()