UI visuals for mail calendar items, calendar reminders.

This commit is contained in:
Burak Kaan Köse
2026-02-11 01:49:29 +01:00
parent 870a5e2bf6
commit 52ee5f1d8a
21 changed files with 639 additions and 77 deletions
@@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Serilog;
using Wino.Core.Domain.Interfaces;
using Wino.Mail.WinUI.Interfaces;
namespace Wino.Mail.WinUI.Services;
public class CalendarReminderServer : ICalendarReminderServer
{
private static readonly TimeSpan PollingInterval = TimeSpan.FromSeconds(30);
private readonly ICalendarService _calendarService;
private readonly IAccountService _accountService;
private readonly INotificationBuilder _notificationBuilder;
private readonly ILogger _logger = Log.ForContext<CalendarReminderServer>();
private readonly SemaphoreSlim _startLock = new(1, 1);
private readonly HashSet<string> _sentReminderKeys = [];
private Task? _loopTask;
private CancellationTokenSource? _loopCts;
private DateTime _lastCheckLocal = DateTime.MinValue;
public CalendarReminderServer(ICalendarService calendarService, IAccountService accountService, INotificationBuilder notificationBuilder)
{
_calendarService = calendarService;
_accountService = accountService;
_notificationBuilder = notificationBuilder;
}
public async Task StartAsync()
{
await _startLock.WaitAsync().ConfigureAwait(false);
try
{
if (_loopTask != null)
return;
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
var hasCalendarAccess = accounts.Exists(a => a.IsCalendarAccessGranted);
if (!hasCalendarAccess)
{
_logger.Information("Calendar reminder server will not start because no account has calendar access.");
return;
}
_lastCheckLocal = DateTime.Now.AddSeconds(-30);
_loopCts = new CancellationTokenSource();
_loopTask = RunLoopAsync(_loopCts.Token);
_logger.Information("Calendar reminder server started.");
}
finally
{
_startLock.Release();
}
}
private async Task RunLoopAsync(CancellationToken cancellationToken)
{
using var timer = new PeriodicTimer(PollingInterval);
try
{
while (await timer.WaitForNextTickAsync(cancellationToken).ConfigureAwait(false))
{
await ExecuteTickAsync(cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
// no-op
}
catch (Exception ex)
{
_logger.Error(ex, "Calendar reminder server loop terminated unexpectedly.");
}
}
private async Task ExecuteTickAsync(CancellationToken cancellationToken)
{
var nowLocal = DateTime.Now;
if (_lastCheckLocal == DateTime.MinValue)
_lastCheckLocal = nowLocal.AddSeconds(-PollingInterval.TotalSeconds);
var dueNotifications = await _calendarService
.CheckAndNotifyAsync(_lastCheckLocal, nowLocal, _sentReminderKeys, cancellationToken)
.ConfigureAwait(false);
foreach (var reminder in dueNotifications)
{
cancellationToken.ThrowIfCancellationRequested();
await _notificationBuilder
.CreateCalendarReminderNotificationAsync(reminder.CalendarItem, reminder.ReminderDurationInSeconds)
.ConfigureAwait(false);
}
_lastCheckLocal = nowLocal;
}
}
@@ -7,6 +7,7 @@ using Microsoft.Toolkit.Uwp.Notifications;
using Serilog;
using Windows.Data.Xml.Dom;
using Windows.UI.Notifications;
using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain;
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Entities.Shared;
@@ -250,4 +251,38 @@ public class NotificationBuilder : INotificationBuilder
builder.AddButton(new ToastButton().SetContent(Translator.Buttons_FixAccount));
builder.Show();
}
public Task CreateCalendarReminderNotificationAsync(CalendarItem calendarItem, long reminderDurationInSeconds)
{
if (calendarItem == null)
return Task.CompletedTask;
var builder = new ToastContentBuilder();
builder.SetToastScenario(ToastScenario.Reminder);
var localStart = calendarItem.LocalStartDate;
var reminderMinutes = (int)Math.Max(0, reminderDurationInSeconds / 60);
var reminderContext = reminderMinutes > 0
? $"Starts in {reminderMinutes} minute{(reminderMinutes == 1 ? string.Empty : "s")}"
: "Starting now";
builder.AddText(calendarItem.Title);
builder.AddText($"{reminderContext} - {localStart:g}");
if (!string.IsNullOrWhiteSpace(calendarItem.Location))
builder.AddText(calendarItem.Location);
builder.AddArgument(Constants.ToastCalendarActionKey, Constants.ToastCalendarNavigateAction);
builder.AddArgument(Constants.ToastCalendarItemIdKey, calendarItem.Id.ToString());
builder.AddButton(GetDismissButton());
builder.AddAudio(new ToastAudio()
{
Src = new Uri("ms-winsoundevent:Notification.Reminder")
});
var tag = $"calendar-reminder-{calendarItem.Id:N}-{reminderDurationInSeconds}";
builder.Show(toast => toast.Tag = tag);
return Task.CompletedTask;
}
}