Filter reminder snooze options by default reminder
This commit is contained in:
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user