Restore dual mail and calendar app entries
This commit is contained in:
@@ -147,6 +147,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
private readonly ICalendarService _calendarService;
|
private readonly ICalendarService _calendarService;
|
||||||
private readonly INavigationService _navigationService;
|
private readonly INavigationService _navigationService;
|
||||||
private readonly INativeAppService _nativeAppService;
|
private readonly INativeAppService _nativeAppService;
|
||||||
|
private readonly INotificationBuilder _notificationBuilder;
|
||||||
private readonly IPreferencesService _preferencesService;
|
private readonly IPreferencesService _preferencesService;
|
||||||
private readonly IWinoRequestDelegator _winoRequestDelegator;
|
private readonly IWinoRequestDelegator _winoRequestDelegator;
|
||||||
private readonly IMailDialogService _dialogService;
|
private readonly IMailDialogService _dialogService;
|
||||||
@@ -157,6 +158,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
private bool _subscriptionsAttached;
|
private bool _subscriptionsAttached;
|
||||||
private CancellationTokenSource _pageLifetimeCts = new();
|
private CancellationTokenSource _pageLifetimeCts = new();
|
||||||
private long _pageLifetimeVersion;
|
private long _pageLifetimeVersion;
|
||||||
|
private bool _isCalendarBadgeClearedForPageLifetime;
|
||||||
private Dictionary<Guid, CalendarItemViewModel> _loadedCalendarItems = new();
|
private Dictionary<Guid, CalendarItemViewModel> _loadedCalendarItems = new();
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
@@ -172,6 +174,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
IKeyPressService keyPressService,
|
IKeyPressService keyPressService,
|
||||||
INativeAppService nativeAppService,
|
INativeAppService nativeAppService,
|
||||||
IAccountCalendarStateService accountCalendarStateService,
|
IAccountCalendarStateService accountCalendarStateService,
|
||||||
|
INotificationBuilder notificationBuilder,
|
||||||
IPreferencesService preferencesService,
|
IPreferencesService preferencesService,
|
||||||
IWinoRequestDelegator winoRequestDelegator,
|
IWinoRequestDelegator winoRequestDelegator,
|
||||||
IMailDialogService dialogService,
|
IMailDialogService dialogService,
|
||||||
@@ -183,6 +186,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
_calendarService = calendarService;
|
_calendarService = calendarService;
|
||||||
_navigationService = navigationService;
|
_navigationService = navigationService;
|
||||||
_nativeAppService = nativeAppService;
|
_nativeAppService = nativeAppService;
|
||||||
|
_notificationBuilder = notificationBuilder;
|
||||||
_preferencesService = preferencesService;
|
_preferencesService = preferencesService;
|
||||||
_winoRequestDelegator = winoRequestDelegator;
|
_winoRequestDelegator = winoRequestDelegator;
|
||||||
_dialogService = dialogService;
|
_dialogService = dialogService;
|
||||||
@@ -360,6 +364,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
{
|
{
|
||||||
CancelPendingOperations();
|
CancelPendingOperations();
|
||||||
_pageLifetimeCts = new CancellationTokenSource();
|
_pageLifetimeCts = new CancellationTokenSource();
|
||||||
|
_isCalendarBadgeClearedForPageLifetime = false;
|
||||||
Interlocked.Increment(ref _pageLifetimeVersion);
|
Interlocked.Increment(ref _pageLifetimeVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -614,6 +619,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
{
|
{
|
||||||
var lifetimeVersion = CurrentPageLifetimeVersion;
|
var lifetimeVersion = CurrentPageLifetimeVersion;
|
||||||
var hasLoadingLock = await WaitForCalendarLoadingLockAsync(lifetimeVersion).ConfigureAwait(false);
|
var hasLoadingLock = await WaitForCalendarLoadingLockAsync(lifetimeVersion).ConfigureAwait(false);
|
||||||
|
var loadSucceeded = false;
|
||||||
|
|
||||||
if (!hasLoadingLock)
|
if (!hasLoadingLock)
|
||||||
return;
|
return;
|
||||||
@@ -666,6 +672,8 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
DisplayDetailsCalendarItemViewModel = null;
|
DisplayDetailsCalendarItemViewModel = null;
|
||||||
}
|
}
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
|
loadSucceeded = true;
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@@ -685,6 +693,12 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
ReleaseCalendarLoadingLock();
|
ReleaseCalendarLoadingLock();
|
||||||
await ExecuteUIThreadIfActiveAsync(lifetimeVersion, () => IsCalendarEnabled = true).ConfigureAwait(false);
|
await ExecuteUIThreadIfActiveAsync(lifetimeVersion, () => IsCalendarEnabled = true).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (loadSucceeded && !_isCalendarBadgeClearedForPageLifetime && IsPageActive(lifetimeVersion))
|
||||||
|
{
|
||||||
|
await _notificationBuilder.ClearCalendarTaskbarBadgeAsync().ConfigureAwait(false);
|
||||||
|
_isCalendarBadgeClearedForPageLifetime = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ReloadCurrentVisibleRangeAsync()
|
public Task ReloadCurrentVisibleRangeAsync()
|
||||||
@@ -692,6 +706,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
|
|||||||
if (CurrentVisibleRange == null)
|
if (CurrentVisibleRange == null)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
RefreshSettings();
|
||||||
return ApplyDisplayRequestAsync(new CalendarDisplayRequest(CurrentVisibleRange.DisplayType, CurrentVisibleRange.AnchorDate), forceReload: true);
|
return ApplyDisplayRequestAsync(new CalendarDisplayRequest(CurrentVisibleRange.DisplayType, CurrentVisibleRange.AnchorDate), forceReload: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,16 @@ public interface INotificationBuilder
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task UpdateTaskbarIconBadgeAsync();
|
Task UpdateTaskbarIconBadgeAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds to the calendar app-entry badge count for newly downloaded events.
|
||||||
|
/// </summary>
|
||||||
|
Task AddCalendarTaskbarBadgeCountAsync(int newlyDownloadedCount);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the calendar app-entry badge.
|
||||||
|
/// </summary>
|
||||||
|
Task ClearCalendarTaskbarBadgeAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes the toast notification for a specific mail by unique id.
|
/// Removes the toast notification for a specific mail by unique id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Wino.Core.Activation;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Wino.Core.Tests;
|
||||||
|
|
||||||
|
public class AppModeActivationResolverTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("--wino-mail", WinoApplicationMode.Calendar, WinoApplicationMode.Mail)]
|
||||||
|
[InlineData("--wino-calendar", WinoApplicationMode.Mail, WinoApplicationMode.Calendar)]
|
||||||
|
[InlineData("--mode=mail", WinoApplicationMode.Calendar, WinoApplicationMode.Mail)]
|
||||||
|
[InlineData("--mode=calendar", WinoApplicationMode.Mail, WinoApplicationMode.Calendar)]
|
||||||
|
[InlineData("CalendarApp", WinoApplicationMode.Mail, WinoApplicationMode.Calendar)]
|
||||||
|
[InlineData("App", WinoApplicationMode.Calendar, WinoApplicationMode.Mail)]
|
||||||
|
public void Resolve_PrefersKnownMailCalendarSignals(string source, WinoApplicationMode defaultMode, WinoApplicationMode expectedMode)
|
||||||
|
{
|
||||||
|
var resolvedMode = AppModeActivationResolver.Resolve(source, null, null, defaultMode);
|
||||||
|
|
||||||
|
resolvedMode.Should().Be(expectedMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Resolve_ToggleDefaultArgumentFlipsBetweenMailAndCalendar()
|
||||||
|
{
|
||||||
|
AppModeActivationResolver.Resolve("--mode=toggle-default", null, null, WinoApplicationMode.Mail)
|
||||||
|
.Should().Be(WinoApplicationMode.Calendar);
|
||||||
|
|
||||||
|
AppModeActivationResolver.Resolve("--mode=toggle-default", null, null, WinoApplicationMode.Calendar)
|
||||||
|
.Should().Be(WinoApplicationMode.Mail);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,7 +47,7 @@ public class CalendarPageViewModelTests
|
|||||||
viewModel.CurrentVisibleRange.EndDate.Should().Be(new DateOnly(2026, 3, 22));
|
viewModel.CurrentVisibleRange.EndDate.Should().Be(new DateOnly(2026, 3, 22));
|
||||||
viewModel.LoadedDateWindow.StartDate.Should().Be(new DateTime(2026, 3, 9));
|
viewModel.LoadedDateWindow.StartDate.Should().Be(new DateTime(2026, 3, 9));
|
||||||
viewModel.LoadedDateWindow.EndDate.Should().Be(new DateTime(2026, 3, 30));
|
viewModel.LoadedDateWindow.EndDate.Should().Be(new DateTime(2026, 3, 30));
|
||||||
viewModel.VisibleDateRangeText.Should().Be("March 16 - March 22");
|
viewModel.VisibleDateRangeText.Should().Be("March 16 - 22");
|
||||||
|
|
||||||
requestedPeriod.Should().NotBeNull();
|
requestedPeriod.Should().NotBeNull();
|
||||||
requestedPeriod!.Start.Should().Be(new DateTime(2026, 3, 9));
|
requestedPeriod!.Start.Should().Be(new DateTime(2026, 3, 9));
|
||||||
@@ -242,6 +242,45 @@ public class CalendarPageViewModelTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyDisplayRequestAsync_ClearsCalendarBadgeOnlyOncePerPageLifetime()
|
||||||
|
{
|
||||||
|
var settings = CreateSettings();
|
||||||
|
var preferencesService = CreatePreferencesService(settings);
|
||||||
|
var calendarService = new Mock<ICalendarService>();
|
||||||
|
var notificationBuilder = new Mock<INotificationBuilder>();
|
||||||
|
|
||||||
|
calendarService
|
||||||
|
.Setup(service => service.GetCalendarEventsAsync(It.IsAny<IAccountCalendar>(), It.IsAny<ITimePeriod>()))
|
||||||
|
.ReturnsAsync([]);
|
||||||
|
|
||||||
|
notificationBuilder
|
||||||
|
.Setup(builder => builder.ClearCalendarTaskbarBadgeAsync())
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
|
||||||
|
var viewModel = CreateViewModel(
|
||||||
|
calendarService.Object,
|
||||||
|
preferencesService.Object,
|
||||||
|
new DateOnly(2026, 3, 20),
|
||||||
|
notificationBuilder: notificationBuilder.Object);
|
||||||
|
|
||||||
|
viewModel.OnNavigatedTo(NavigationMode.New, null!);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new CalendarDisplayRequest(CalendarDisplayType.Day, new DateOnly(2026, 3, 20));
|
||||||
|
|
||||||
|
await viewModel.ApplyDisplayRequestAsync(request);
|
||||||
|
await viewModel.ApplyDisplayRequestAsync(request, forceReload: true);
|
||||||
|
|
||||||
|
notificationBuilder.Verify(builder => builder.ClearCalendarTaskbarBadgeAsync(), Times.Once);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
viewModel.OnNavigatedFrom(NavigationMode.Back, null!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CalendarItemAddedMessage_ReconcilesTrackedLocalPreviewInPlace()
|
public async Task CalendarItemAddedMessage_ReconcilesTrackedLocalPreviewInPlace()
|
||||||
{
|
{
|
||||||
@@ -332,6 +371,15 @@ public class CalendarPageViewModelTests
|
|||||||
ICalendarService calendarService,
|
ICalendarService calendarService,
|
||||||
IPreferencesService preferencesService,
|
IPreferencesService preferencesService,
|
||||||
DateOnly today)
|
DateOnly today)
|
||||||
|
{
|
||||||
|
return CreateViewModel(calendarService, preferencesService, today, notificationBuilder: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CalendarPageViewModel CreateViewModel(
|
||||||
|
ICalendarService calendarService,
|
||||||
|
IPreferencesService preferencesService,
|
||||||
|
DateOnly today,
|
||||||
|
INotificationBuilder? notificationBuilder)
|
||||||
{
|
{
|
||||||
var account = CreateAccount();
|
var account = CreateAccount();
|
||||||
|
|
||||||
@@ -339,7 +387,7 @@ public class CalendarPageViewModelTests
|
|||||||
var accountCalendarViewModel = new AccountCalendarViewModel(account, calendar);
|
var accountCalendarViewModel = new AccountCalendarViewModel(account, calendar);
|
||||||
var accountCalendarStateService = new FakeAccountCalendarStateService([accountCalendarViewModel]);
|
var accountCalendarStateService = new FakeAccountCalendarStateService([accountCalendarViewModel]);
|
||||||
|
|
||||||
return CreateViewModel(calendarService, preferencesService, today, accountCalendarStateService);
|
return CreateViewModel(calendarService, preferencesService, today, accountCalendarStateService, notificationBuilder: notificationBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CalendarPageViewModel CreateViewModel(
|
private static CalendarPageViewModel CreateViewModel(
|
||||||
@@ -356,6 +404,7 @@ public class CalendarPageViewModelTests
|
|||||||
IAccountCalendarStateService accountCalendarStateService,
|
IAccountCalendarStateService accountCalendarStateService,
|
||||||
INavigationService? navigationService = null,
|
INavigationService? navigationService = null,
|
||||||
INativeAppService? nativeAppService = null,
|
INativeAppService? nativeAppService = null,
|
||||||
|
INotificationBuilder? notificationBuilder = null,
|
||||||
IWinoRequestDelegator? requestDelegator = null,
|
IWinoRequestDelegator? requestDelegator = null,
|
||||||
IMailDialogService? dialogService = null)
|
IMailDialogService? dialogService = null)
|
||||||
{
|
{
|
||||||
@@ -371,6 +420,7 @@ public class CalendarPageViewModelTests
|
|||||||
Mock.Of<IKeyPressService>(),
|
Mock.Of<IKeyPressService>(),
|
||||||
nativeAppService ?? Mock.Of<INativeAppService>(),
|
nativeAppService ?? Mock.Of<INativeAppService>(),
|
||||||
accountCalendarStateService,
|
accountCalendarStateService,
|
||||||
|
notificationBuilder ?? Mock.Of<INotificationBuilder>(),
|
||||||
preferencesService,
|
preferencesService,
|
||||||
requestDelegator ?? Mock.Of<IWinoRequestDelegator>(),
|
requestDelegator ?? Mock.Of<IWinoRequestDelegator>(),
|
||||||
dialogService ?? Mock.Of<IMailDialogService>(),
|
dialogService ?? Mock.Of<IMailDialogService>(),
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ public class CalendarRangeResolverTests
|
|||||||
|
|
||||||
var text = formatter.Format(range, new TestDateContextProvider("de-DE", today: new DateOnly(2026, 3, 20)));
|
var text = formatter.Format(range, new TestDateContextProvider("de-DE", today: new DateOnly(2026, 3, 20)));
|
||||||
|
|
||||||
text.Should().Be("16. März - 22. März");
|
text.Should().Be("16. März - 22");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CalendarSettings CreateSettings(
|
private static CalendarSettings CreateSettings(
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Wino.Core.Activation;
|
||||||
|
|
||||||
|
public static class AppModeActivationResolver
|
||||||
|
{
|
||||||
|
public static WinoApplicationMode Resolve(string? launchArguments, string? tileId, string? appId, WinoApplicationMode defaultMode = WinoApplicationMode.Mail)
|
||||||
|
{
|
||||||
|
if (TryResolveFromText(launchArguments, defaultMode, out var mode))
|
||||||
|
return mode;
|
||||||
|
|
||||||
|
if (TryResolveFromText(tileId, defaultMode, out mode))
|
||||||
|
return mode;
|
||||||
|
|
||||||
|
if (TryResolveFromText(appId, defaultMode, out mode))
|
||||||
|
return mode;
|
||||||
|
|
||||||
|
return defaultMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryResolveFromText(string? value, WinoApplicationMode defaultMode, out WinoApplicationMode mode)
|
||||||
|
{
|
||||||
|
mode = defaultMode;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (Contains(value, "--mode=toggle-default") ||
|
||||||
|
Contains(value, "mode=toggle-default"))
|
||||||
|
{
|
||||||
|
mode = GetOpposite(defaultMode);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Contains(value, "--wino-calendar") ||
|
||||||
|
Contains(value, "wino-calendar") ||
|
||||||
|
Contains(value, "--mode=calendar") ||
|
||||||
|
Contains(value, "mode=calendar") ||
|
||||||
|
Contains(value, "calendarapp") ||
|
||||||
|
EqualsToken(value, "calendar"))
|
||||||
|
{
|
||||||
|
mode = WinoApplicationMode.Calendar;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Contains(value, "wino-contacts") ||
|
||||||
|
Contains(value, "--mode=contacts") ||
|
||||||
|
Contains(value, "mode=contacts") ||
|
||||||
|
Contains(value, "contactsapp") ||
|
||||||
|
EqualsToken(value, "contacts"))
|
||||||
|
{
|
||||||
|
mode = WinoApplicationMode.Contacts;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Contains(value, "wino-settings") ||
|
||||||
|
Contains(value, "--mode=settings") ||
|
||||||
|
Contains(value, "mode=settings") ||
|
||||||
|
Contains(value, "settingsapp") ||
|
||||||
|
EqualsToken(value, "settings"))
|
||||||
|
{
|
||||||
|
mode = WinoApplicationMode.Settings;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Contains(value, "--wino-mail") ||
|
||||||
|
Contains(value, "wino-mail") ||
|
||||||
|
Contains(value, "--mode=mail") ||
|
||||||
|
Contains(value, "mode=mail") ||
|
||||||
|
Contains(value, "!app") ||
|
||||||
|
Contains(value, "mailapp") ||
|
||||||
|
EqualsToken(value, "app") ||
|
||||||
|
EqualsToken(value, "mail"))
|
||||||
|
{
|
||||||
|
mode = WinoApplicationMode.Mail;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool Contains(string source, string token)
|
||||||
|
=> source.Contains(token, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
private static bool EqualsToken(string source, string token)
|
||||||
|
=> string.Equals(source.Trim(), token, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
private static WinoApplicationMode GetOpposite(WinoApplicationMode defaultMode)
|
||||||
|
=> defaultMode switch
|
||||||
|
{
|
||||||
|
WinoApplicationMode.Mail => WinoApplicationMode.Calendar,
|
||||||
|
WinoApplicationMode.Calendar => WinoApplicationMode.Mail,
|
||||||
|
_ => WinoApplicationMode.Mail
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -465,13 +465,15 @@ public class SynchronizationManager : ISynchronizationManager
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await synchronizer.SynchronizeCalendarEventsAsync(options, linkedCancellationTokenSource.Token);
|
var result = await synchronizer.SynchronizeCalendarEventsAsync(options, linkedCancellationTokenSource.Token);
|
||||||
|
var downloadedEventCount = result.DownloadedEvents?.Count() ?? 0;
|
||||||
|
|
||||||
_logger.Information("Calendar synchronization completed for account {AccountId} with state {State}",
|
_logger.Information("Calendar synchronization completed for account {AccountId} with state {State}",
|
||||||
options.AccountId, result.CompletedState);
|
options.AccountId, result.CompletedState);
|
||||||
|
|
||||||
// TODO: Create notifications for new calendar events when INotificationBuilder supports it
|
if (downloadedEventCount > 0)
|
||||||
// if (result.DownloadedEvents?.Any() ?? false)
|
{
|
||||||
// await _notificationBuilder.CreateCalendarNotificationsAsync(result.DownloadedEvents);
|
await _notificationBuilder.AddCalendarTaskbarBadgeCountAsync(downloadedEventCount).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using Windows.ApplicationModel;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Wino.Mail.WinUI.Activation;
|
||||||
|
|
||||||
|
internal static class AppEntryConstants
|
||||||
|
{
|
||||||
|
public const string MailApplicationId = "App";
|
||||||
|
public const string CalendarApplicationId = "CalendarApp";
|
||||||
|
public const string MailLaunchArgument = "--wino-mail";
|
||||||
|
public const string CalendarLaunchArgument = "--wino-calendar";
|
||||||
|
|
||||||
|
public static string GetModeLaunchArgument(WinoApplicationMode mode)
|
||||||
|
=> mode switch
|
||||||
|
{
|
||||||
|
WinoApplicationMode.Calendar => CalendarLaunchArgument,
|
||||||
|
WinoApplicationMode.Contacts => "--mode=contacts",
|
||||||
|
WinoApplicationMode.Settings => "--mode=settings",
|
||||||
|
_ => MailLaunchArgument
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string? GetPackagedApplicationId(WinoApplicationMode mode)
|
||||||
|
=> mode switch
|
||||||
|
{
|
||||||
|
WinoApplicationMode.Calendar => CalendarApplicationId,
|
||||||
|
WinoApplicationMode.Mail => MailApplicationId,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string GetAppUserModelId(string packageFamilyName, WinoApplicationMode mode)
|
||||||
|
=> $"{packageFamilyName}!{GetPackagedApplicationId(mode) ?? MailApplicationId}";
|
||||||
|
|
||||||
|
public static string GetAppUserModelId(WinoApplicationMode mode)
|
||||||
|
=> GetAppUserModelId(Package.Current.Id.FamilyName, mode);
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
namespace Wino.Mail.WinUI.Activation;
|
namespace Wino.Mail.WinUI.Activation;
|
||||||
@@ -6,87 +5,5 @@ namespace Wino.Mail.WinUI.Activation;
|
|||||||
internal static class AppModeActivationResolver
|
internal static class AppModeActivationResolver
|
||||||
{
|
{
|
||||||
public static WinoApplicationMode Resolve(string? launchArguments, string? tileId, string? appId, WinoApplicationMode defaultMode = WinoApplicationMode.Mail)
|
public static WinoApplicationMode Resolve(string? launchArguments, string? tileId, string? appId, WinoApplicationMode defaultMode = WinoApplicationMode.Mail)
|
||||||
{
|
=> Wino.Core.Activation.AppModeActivationResolver.Resolve(launchArguments, tileId, appId, defaultMode);
|
||||||
if (TryResolveFromText(launchArguments, defaultMode, out var mode))
|
|
||||||
return mode;
|
|
||||||
|
|
||||||
if (TryResolveFromText(tileId, defaultMode, out mode))
|
|
||||||
return mode;
|
|
||||||
|
|
||||||
if (TryResolveFromText(appId, defaultMode, out mode))
|
|
||||||
return mode;
|
|
||||||
|
|
||||||
return defaultMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryResolveFromText(string? value, WinoApplicationMode defaultMode, out WinoApplicationMode mode)
|
|
||||||
{
|
|
||||||
mode = defaultMode;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(value))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (Contains(value, "--mode=toggle-default") ||
|
|
||||||
Contains(value, "mode=toggle-default"))
|
|
||||||
{
|
|
||||||
mode = GetOpposite(defaultMode);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Contains(value, "wino-calendar") ||
|
|
||||||
Contains(value, "--mode=calendar") ||
|
|
||||||
Contains(value, "mode=calendar") ||
|
|
||||||
Contains(value, "calendarapp") ||
|
|
||||||
EqualsToken(value, "calendar"))
|
|
||||||
{
|
|
||||||
mode = WinoApplicationMode.Calendar;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Contains(value, "wino-contacts") ||
|
|
||||||
Contains(value, "--mode=contacts") ||
|
|
||||||
Contains(value, "mode=contacts") ||
|
|
||||||
Contains(value, "contactsapp") ||
|
|
||||||
EqualsToken(value, "contacts"))
|
|
||||||
{
|
|
||||||
mode = WinoApplicationMode.Contacts;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Contains(value, "wino-settings") ||
|
|
||||||
Contains(value, "--mode=settings") ||
|
|
||||||
Contains(value, "mode=settings") ||
|
|
||||||
Contains(value, "settingsapp") ||
|
|
||||||
EqualsToken(value, "settings"))
|
|
||||||
{
|
|
||||||
mode = WinoApplicationMode.Settings;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Contains(value, "wino-mail") ||
|
|
||||||
Contains(value, "--mode=mail") ||
|
|
||||||
Contains(value, "mode=mail") ||
|
|
||||||
Contains(value, "mailapp") ||
|
|
||||||
EqualsToken(value, "mail"))
|
|
||||||
{
|
|
||||||
mode = WinoApplicationMode.Mail;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool Contains(string source, string token)
|
|
||||||
=> source.Contains(token, StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
private static bool EqualsToken(string source, string token)
|
|
||||||
=> string.Equals(source.Trim(), token, StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
private static WinoApplicationMode GetOpposite(WinoApplicationMode defaultMode)
|
|
||||||
=> defaultMode switch
|
|
||||||
{
|
|
||||||
WinoApplicationMode.Mail => WinoApplicationMode.Calendar,
|
|
||||||
WinoApplicationMode.Calendar => WinoApplicationMode.Mail,
|
|
||||||
_ => WinoApplicationMode.Mail
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
using Microsoft.Windows.AppNotifications;
|
||||||
|
using Wino.Core.Domain;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Wino.Mail.WinUI.Activation;
|
||||||
|
|
||||||
|
internal static class ToastActivationResolver
|
||||||
|
{
|
||||||
|
public static bool TryParse(string? argument, out NotificationArguments toastArguments)
|
||||||
|
{
|
||||||
|
toastArguments = default!;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(argument))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var parsedArguments = NotificationArguments.Parse(argument);
|
||||||
|
if (!ContainsKnownToastKey(parsedArguments))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
toastArguments = parsedArguments;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool ShouldBringToForeground(NotificationArguments toastArguments)
|
||||||
|
{
|
||||||
|
if (toastArguments.TryGetValue(Constants.ToastStoreUpdateActionKey, out string storeUpdateAction) &&
|
||||||
|
storeUpdateAction == Constants.ToastStoreUpdateActionInstall)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toastArguments.TryGetValue(Constants.ToastCalendarActionKey, out string calendarAction))
|
||||||
|
{
|
||||||
|
return calendarAction == Constants.ToastCalendarNavigateAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toastArguments.TryGetValue(Constants.ToastActionKey, out MailOperation mailAction))
|
||||||
|
{
|
||||||
|
return mailAction == MailOperation.Navigate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ContainsKnownToastKey(NotificationArguments toastArguments)
|
||||||
|
=> toastArguments.TryGetValue(Constants.ToastStoreUpdateActionKey, out string _) ||
|
||||||
|
toastArguments.TryGetValue(Constants.ToastCalendarActionKey, out string _) ||
|
||||||
|
toastArguments.TryGetValue(Constants.ToastActionKey, out string _);
|
||||||
|
}
|
||||||
+67
-36
@@ -167,19 +167,31 @@ public partial class App : WinoApplication,
|
|||||||
if (!_hasConfiguredAccounts)
|
if (!_hasConfiguredAccounts)
|
||||||
return ActivateWelcomeWindowAsync();
|
return ActivateWelcomeWindowAsync();
|
||||||
|
|
||||||
return ActivateShellWindowAsync(_preferencesService?.DefaultApplicationMode);
|
return LaunchEntryOrActivateShellAsync(_preferencesService?.DefaultApplicationMode ?? WinoApplicationMode.Mail);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task OpenMailFromTrayAsync()
|
private Task OpenMailFromTrayAsync()
|
||||||
=> _hasConfiguredAccounts
|
=> _hasConfiguredAccounts
|
||||||
? ActivateShellWindowAsync(WinoApplicationMode.Mail)
|
? LaunchEntryOrActivateShellAsync(WinoApplicationMode.Mail)
|
||||||
: ActivateWelcomeWindowAsync();
|
: ActivateWelcomeWindowAsync();
|
||||||
|
|
||||||
private Task OpenCalendarFromTrayAsync()
|
private Task OpenCalendarFromTrayAsync()
|
||||||
=> _hasConfiguredAccounts
|
=> _hasConfiguredAccounts
|
||||||
? ActivateShellWindowAsync(WinoApplicationMode.Calendar)
|
? LaunchEntryOrActivateShellAsync(WinoApplicationMode.Calendar)
|
||||||
: ActivateWelcomeWindowAsync();
|
: ActivateWelcomeWindowAsync();
|
||||||
|
|
||||||
|
private async Task LaunchEntryOrActivateShellAsync(WinoApplicationMode mode)
|
||||||
|
{
|
||||||
|
if (AppEntryConstants.GetPackagedApplicationId(mode) != null)
|
||||||
|
{
|
||||||
|
var appEntryLauncher = Services.GetRequiredService<PackagedAppEntryLauncher>();
|
||||||
|
if (await appEntryLauncher.LaunchAsync(mode))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ActivateShellWindowAsync(mode);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ActivateWelcomeWindowAsync()
|
private async Task ActivateWelcomeWindowAsync()
|
||||||
{
|
{
|
||||||
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
|
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
|
||||||
@@ -218,7 +230,7 @@ public partial class App : WinoApplication,
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (mode.HasValue)
|
if (mode.HasValue)
|
||||||
shellWindow.HandleAppActivation(GetModeLaunchArgument(mode.Value));
|
shellWindow.HandleAppActivation(AppEntryConstants.GetModeLaunchArgument(mode.Value));
|
||||||
|
|
||||||
CloseWelcomeWindowIfPresent();
|
CloseWelcomeWindowIfPresent();
|
||||||
await ActivateWindowAsync((WindowEx)shellWindow);
|
await ActivateWindowAsync((WindowEx)shellWindow);
|
||||||
@@ -455,7 +467,15 @@ public partial class App : WinoApplication,
|
|||||||
TryMarkInitialNotificationActivationHandled())
|
TryMarkInitialNotificationActivationHandled())
|
||||||
{
|
{
|
||||||
LogActivation($"Processing notification activation from OnLaunched. Arguments: {toastArgs.Argument}");
|
LogActivation($"Processing notification activation from OnLaunched. Arguments: {toastArgs.Argument}");
|
||||||
await HandleToastActivationAsync(toastArgs);
|
await HandleToastActivationAsync(toastArgs.Argument, toastArgs.UserInput);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ToastActivationResolver.TryParse(args.Arguments, out var launchToastArguments) &&
|
||||||
|
TryMarkInitialNotificationActivationHandled())
|
||||||
|
{
|
||||||
|
LogActivation($"Processing toast launch activation from OnLaunched. Arguments: {args.Arguments}");
|
||||||
|
await HandleToastActivationAsync(launchToastArguments);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,18 +532,18 @@ public partial class App : WinoApplication,
|
|||||||
LogActivation($"Processing initial notification activation from application startup. Arguments: {toastArgs.Argument}");
|
LogActivation($"Processing initial notification activation from application startup. Arguments: {toastArgs.Argument}");
|
||||||
|
|
||||||
await EnsureActivationInfrastructureAsync();
|
await EnsureActivationInfrastructureAsync();
|
||||||
await HandleToastActivationAsync(toastArgs);
|
await HandleToastActivationAsync(toastArgs.Argument, toastArgs.UserInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AppNotificationInvoked(AppNotificationManager sender, AppNotificationActivatedEventArgs args)
|
private void AppNotificationInvoked(AppNotificationManager sender, AppNotificationActivatedEventArgs args)
|
||||||
{
|
{
|
||||||
// AppNotification callbacks are not guaranteed to run on the UI thread.
|
// AppNotification callbacks are not guaranteed to run on the UI thread.
|
||||||
// Marshal toast handling to the window dispatcher before touching window APIs.
|
// Marshal toast handling to the window dispatcher before touching window APIs.
|
||||||
if (MainWindow?.DispatcherQueue?.TryEnqueue(() => _ = HandleToastActivationAsync(args)) == true)
|
if (MainWindow?.DispatcherQueue?.TryEnqueue(() => _ = HandleToastActivationAsync(args.Argument, args.UserInput)) == true)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
LogActivation($"Processing notification activation from NotificationInvoked. Arguments: {args.Argument}");
|
LogActivation($"Processing notification activation from NotificationInvoked. Arguments: {args.Argument}");
|
||||||
_ = HandleToastActivationAsync(args);
|
_ = HandleToastActivationAsync(args.Argument, args.UserInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TryRegisterAppNotifications()
|
private void TryRegisterAppNotifications()
|
||||||
@@ -546,11 +566,9 @@ public partial class App : WinoApplication,
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles toast notification activation scenarios.
|
/// Handles toast notification activation scenarios.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task HandleToastActivationAsync(AppNotificationActivatedEventArgs toastArgs)
|
private async Task HandleToastActivationAsync(NotificationArguments toastArguments, IDictionary<string, string>? userInput = null)
|
||||||
{
|
{
|
||||||
LogActivation($"Handling app notification activation. Arguments: {toastArgs.Argument}");
|
LogActivation("Handling app notification activation.");
|
||||||
|
|
||||||
var toastArguments = NotificationArguments.Parse(toastArgs.Argument);
|
|
||||||
|
|
||||||
if (toastArguments.TryGetValue(Constants.ToastStoreUpdateActionKey, out string storeUpdateAction) &&
|
if (toastArguments.TryGetValue(Constants.ToastStoreUpdateActionKey, out string storeUpdateAction) &&
|
||||||
storeUpdateAction == Constants.ToastStoreUpdateActionInstall)
|
storeUpdateAction == Constants.ToastStoreUpdateActionInstall)
|
||||||
@@ -572,7 +590,7 @@ public partial class App : WinoApplication,
|
|||||||
|
|
||||||
if (calendarAction == Constants.ToastCalendarSnoozeAction)
|
if (calendarAction == Constants.ToastCalendarSnoozeAction)
|
||||||
{
|
{
|
||||||
await HandleCalendarToastSnoozeAsync(toastArgs, calendarItemId);
|
await HandleCalendarToastSnoozeAsync(userInput, calendarItemId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -600,6 +618,17 @@ public partial class App : WinoApplication,
|
|||||||
LogActivation("App notification activation did not match any known handler.");
|
LogActivation("App notification activation did not match any known handler.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Task HandleToastActivationAsync(string toastArgument, IDictionary<string, string>? userInput = null)
|
||||||
|
{
|
||||||
|
if (!ToastActivationResolver.TryParse(toastArgument, out var toastArguments))
|
||||||
|
{
|
||||||
|
LogActivation($"Ignoring non-toast launch argument: {toastArgument}");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HandleToastActivationAsync(toastArguments, userInput);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<IWinoShellWindow?> EnsureShellWindowAsync(WinoApplicationMode mode, bool activateWindow, bool suppressStartupFlows = true)
|
private async Task<IWinoShellWindow?> EnsureShellWindowAsync(WinoApplicationMode mode, bool activateWindow, bool suppressStartupFlows = true)
|
||||||
{
|
{
|
||||||
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
|
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
|
||||||
@@ -612,7 +641,7 @@ public partial class App : WinoApplication,
|
|||||||
|
|
||||||
CreateWindow(
|
CreateWindow(
|
||||||
null,
|
null,
|
||||||
GetModeLaunchArgument(mode),
|
AppEntryConstants.GetModeLaunchArgument(mode),
|
||||||
new ShellModeActivationContext
|
new ShellModeActivationContext
|
||||||
{
|
{
|
||||||
SuppressStartupFlows = suppressStartupFlows
|
SuppressStartupFlows = suppressStartupFlows
|
||||||
@@ -671,9 +700,9 @@ public partial class App : WinoApplication,
|
|||||||
navigationService.Navigate(WinoPage.EventDetailsPage, target);
|
navigationService.Navigate(WinoPage.EventDetailsPage, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleCalendarToastSnoozeAsync(AppNotificationActivatedEventArgs toastArgs, Guid calendarItemId)
|
private async Task HandleCalendarToastSnoozeAsync(IDictionary<string, string>? userInput, Guid calendarItemId)
|
||||||
{
|
{
|
||||||
if (!TryGetSnoozeDurationMinutes(toastArgs, out var snoozeDurationMinutes))
|
if (!TryGetSnoozeDurationMinutes(userInput, out var snoozeDurationMinutes))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var calendarService = Services.GetRequiredService<ICalendarService>();
|
var calendarService = Services.GetRequiredService<ICalendarService>();
|
||||||
@@ -682,15 +711,15 @@ public partial class App : WinoApplication,
|
|||||||
await calendarService.SnoozeCalendarItemAsync(calendarItemId, snoozedUntilLocal);
|
await calendarService.SnoozeCalendarItemAsync(calendarItemId, snoozedUntilLocal);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryGetSnoozeDurationMinutes(AppNotificationActivatedEventArgs toastArgs, out int snoozeDurationMinutes)
|
private bool TryGetSnoozeDurationMinutes(IDictionary<string, string>? userInput, out int snoozeDurationMinutes)
|
||||||
{
|
{
|
||||||
snoozeDurationMinutes = 0;
|
snoozeDurationMinutes = _preferencesService?.DefaultSnoozeDurationInMinutes ?? 0;
|
||||||
|
|
||||||
if (toastArgs.UserInput == null ||
|
if (userInput == null ||
|
||||||
!toastArgs.UserInput.TryGetValue(Constants.ToastCalendarSnoozeDurationInputId, out var selectedValue) ||
|
!userInput.TryGetValue(Constants.ToastCalendarSnoozeDurationInputId, out var selectedValue) ||
|
||||||
selectedValue == null)
|
selectedValue == null)
|
||||||
{
|
{
|
||||||
return false;
|
return snoozeDurationMinutes > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var selectedText = selectedValue.ToString();
|
var selectedText = selectedValue.ToString();
|
||||||
@@ -910,7 +939,7 @@ public partial class App : WinoApplication,
|
|||||||
|
|
||||||
if (TryResolveActivationMode(activationArgs, defaultMode, out var activationMode))
|
if (TryResolveActivationMode(activationArgs, defaultMode, out var activationMode))
|
||||||
{
|
{
|
||||||
shellWindow.HandleAppActivation(GetModeLaunchArgument(activationMode));
|
shellWindow.HandleAppActivation(AppEntryConstants.GetModeLaunchArgument(activationMode));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1049,7 +1078,7 @@ public partial class App : WinoApplication,
|
|||||||
MainWindow?.DispatcherQueue?.TryEnqueue(async () =>
|
MainWindow?.DispatcherQueue?.TryEnqueue(async () =>
|
||||||
{
|
{
|
||||||
// Create and activate ShellWindow — ActiveWindowChanged fires and rebinds the dispatcher.
|
// Create and activate ShellWindow — ActiveWindowChanged fires and rebinds the dispatcher.
|
||||||
CreateWindow(null, GetModeLaunchArgument(WinoApplicationMode.Mail));
|
CreateWindow(null, AppEntryConstants.GetModeLaunchArgument(WinoApplicationMode.Mail));
|
||||||
CloseWelcomeWindowIfPresent();
|
CloseWelcomeWindowIfPresent();
|
||||||
if (MainWindow != null)
|
if (MainWindow != null)
|
||||||
await ActivateWindowAsync(MainWindow);
|
await ActivateWindowAsync(MainWindow);
|
||||||
@@ -1085,7 +1114,7 @@ public partial class App : WinoApplication,
|
|||||||
|
|
||||||
CreateWindow(
|
CreateWindow(
|
||||||
null,
|
null,
|
||||||
GetModeLaunchArgument(WinoApplicationMode.Mail),
|
AppEntryConstants.GetModeLaunchArgument(WinoApplicationMode.Mail),
|
||||||
new ShellModeActivationContext
|
new ShellModeActivationContext
|
||||||
{
|
{
|
||||||
SuppressStartupFlows = true
|
SuppressStartupFlows = true
|
||||||
@@ -1394,14 +1423,24 @@ public partial class App : WinoApplication,
|
|||||||
// Handle toast notification activation
|
// Handle toast notification activation
|
||||||
var toastArgs = (AppNotificationActivatedEventArgs)args.Data;
|
var toastArgs = (AppNotificationActivatedEventArgs)args.Data;
|
||||||
LogActivation($"Processing redirected notification activation. Arguments: {toastArgs.Argument}");
|
LogActivation($"Processing redirected notification activation. Arguments: {toastArgs.Argument}");
|
||||||
_ = HandleToastActivationAsync(toastArgs);
|
_ = HandleToastActivationAsync(toastArgs.Argument, toastArgs.UserInput);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var shouldActivateWindow = true;
|
||||||
|
|
||||||
if (MainWindow is IWinoShellWindow shellWindow)
|
if (MainWindow is IWinoShellWindow shellWindow)
|
||||||
{
|
{
|
||||||
if (args.Kind == ExtendedActivationKind.Launch &&
|
if (args.Kind == ExtendedActivationKind.Launch &&
|
||||||
args.Data is ILaunchActivatedEventArgs launchArgs)
|
args.Data is ILaunchActivatedEventArgs launchArgs)
|
||||||
|
{
|
||||||
|
if (ToastActivationResolver.TryParse(launchArgs.Arguments, out var launchToastArguments))
|
||||||
|
{
|
||||||
|
shouldActivateWindow = ToastActivationResolver.ShouldBringToForeground(launchToastArguments);
|
||||||
|
LogActivation($"Processing redirected toast launch activation. Arguments: {launchArgs.Arguments}");
|
||||||
|
_ = HandleToastActivationAsync(launchToastArguments);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
var launchArguments = launchArgs.Arguments;
|
var launchArguments = launchArgs.Arguments;
|
||||||
|
|
||||||
@@ -1412,15 +1451,16 @@ public partial class App : WinoApplication,
|
|||||||
|
|
||||||
shellWindow.HandleAppActivation(launchArguments, launchArgs.TileId);
|
shellWindow.HandleAppActivation(launchArguments, launchArgs.TileId);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else if (TryResolveActivationMode(args, _preferencesService?.DefaultApplicationMode ?? WinoApplicationMode.Mail, out var redirectedMode))
|
else if (TryResolveActivationMode(args, _preferencesService?.DefaultApplicationMode ?? WinoApplicationMode.Mail, out var redirectedMode))
|
||||||
{
|
{
|
||||||
shellWindow.HandleAppActivation(GetModeLaunchArgument(redirectedMode));
|
shellWindow.HandleAppActivation(AppEntryConstants.GetModeLaunchArgument(redirectedMode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirected launches can target a shell window that is currently hidden in the tray.
|
// Redirected launches can target a shell window that is currently hidden in the tray.
|
||||||
// Restore it through the window manager so Show/BringToFront/Activate happen together.
|
// Restore it through the window manager so Show/BringToFront/Activate happen together.
|
||||||
if (MainWindow is WindowEx mainWindow)
|
if (shouldActivateWindow && MainWindow is WindowEx mainWindow)
|
||||||
{
|
{
|
||||||
Services.GetRequiredService<IWinoWindowManager>().ActivateWindow(mainWindow);
|
Services.GetRequiredService<IWinoWindowManager>().ActivateWindow(mainWindow);
|
||||||
}
|
}
|
||||||
@@ -1434,15 +1474,6 @@ public partial class App : WinoApplication,
|
|||||||
_ = HandleRedirectedActivationAsync();
|
_ = HandleRedirectedActivationAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetModeLaunchArgument(WinoApplicationMode mode)
|
|
||||||
=> mode switch
|
|
||||||
{
|
|
||||||
WinoApplicationMode.Calendar => "--mode=calendar",
|
|
||||||
WinoApplicationMode.Contacts => "--mode=contacts",
|
|
||||||
WinoApplicationMode.Settings => "--mode=settings",
|
|
||||||
_ => "--mode=mail"
|
|
||||||
};
|
|
||||||
|
|
||||||
private static string AppendLaunchArgument(string? launchArguments, string launchArgument)
|
private static string AppendLaunchArgument(string? launchArguments, string launchArgument)
|
||||||
{
|
{
|
||||||
return string.IsNullOrWhiteSpace(launchArguments)
|
return string.IsNullOrWhiteSpace(launchArguments)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ public static class CoreUWPContainerSetup
|
|||||||
services.AddTransient<IWebView2RuntimeValidatorService, WebView2RuntimeValidatorService>();
|
services.AddTransient<IWebView2RuntimeValidatorService, WebView2RuntimeValidatorService>();
|
||||||
services.AddTransient<INotificationBuilder, NotificationBuilder>();
|
services.AddTransient<INotificationBuilder, NotificationBuilder>();
|
||||||
services.AddSingleton<ICalendarReminderServer, CalendarReminderServer>();
|
services.AddSingleton<ICalendarReminderServer, CalendarReminderServer>();
|
||||||
|
services.AddSingleton<PackagedAppEntryLauncher>();
|
||||||
services.AddTransient<IClipboardService, ClipboardService>();
|
services.AddTransient<IClipboardService, ClipboardService>();
|
||||||
services.AddTransient<IStartupBehaviorService, StartupBehaviorService>();
|
services.AddTransient<IStartupBehaviorService, StartupBehaviorService>();
|
||||||
services.AddSingleton<IPrintService, PrintService>();
|
services.AddSingleton<IPrintService, PrintService>();
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using WinUIEx;
|
||||||
|
|
||||||
|
namespace Wino.Mail.WinUI.Helpers;
|
||||||
|
|
||||||
|
internal static class WindowAppUserModelIdHelper
|
||||||
|
{
|
||||||
|
private static readonly Guid PropertyStoreGuid = new("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99");
|
||||||
|
private static readonly PropertyKey AppUserModelIdPropertyKey = new(new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3"), 5);
|
||||||
|
|
||||||
|
public static void TrySet(WindowEx window, string appUserModelId)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(window);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(appUserModelId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(window);
|
||||||
|
if (hwnd == IntPtr.Zero)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var propertyStoreGuid = PropertyStoreGuid;
|
||||||
|
var appUserModelIdPropertyKey = AppUserModelIdPropertyKey;
|
||||||
|
var hr = SHGetPropertyStoreForWindow(hwnd, ref propertyStoreGuid, out var propertyStore);
|
||||||
|
if (hr < 0 || propertyStore == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using (propertyStore)
|
||||||
|
{
|
||||||
|
using var value = PropVariant.FromString(appUserModelId);
|
||||||
|
propertyStore.SetValue(ref appUserModelIdPropertyKey, value);
|
||||||
|
propertyStore.Commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Best effort only. Some Windows builds may keep the original taskbar identity.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("shell32.dll")]
|
||||||
|
private static extern int SHGetPropertyStoreForWindow(
|
||||||
|
IntPtr hwnd,
|
||||||
|
ref Guid riid,
|
||||||
|
[MarshalAs(UnmanagedType.Interface)] out IPropertyStore propertyStore);
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||||
|
private readonly struct PropertyKey(Guid fmtid, uint pid)
|
||||||
|
{
|
||||||
|
public Guid FormatId { get; } = fmtid;
|
||||||
|
public uint PropertyId { get; } = pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ComImport]
|
||||||
|
[Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")]
|
||||||
|
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
private interface IPropertyStore : IDisposable
|
||||||
|
{
|
||||||
|
uint GetCount();
|
||||||
|
void GetAt(uint propertyIndex, out PropertyKey key);
|
||||||
|
void GetValue(ref PropertyKey key, out PropVariant pv);
|
||||||
|
void SetValue(ref PropertyKey key, PropVariant pv);
|
||||||
|
void Commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
private sealed class PropVariant : IDisposable
|
||||||
|
{
|
||||||
|
[FieldOffset(0)]
|
||||||
|
private ushort _valueType;
|
||||||
|
|
||||||
|
[FieldOffset(8)]
|
||||||
|
private IntPtr _pointerValue;
|
||||||
|
|
||||||
|
private PropVariant(string value)
|
||||||
|
{
|
||||||
|
_valueType = 31;
|
||||||
|
_pointerValue = Marshal.StringToCoTaskMemUni(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PropVariant FromString(string value) => new(value);
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
PropVariantClear(this);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
~PropVariant()
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("ole32.dll")]
|
||||||
|
private static extern int PropVariantClear([In, Out] PropVariant propvar);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
<Application Id="App"
|
<Application Id="App"
|
||||||
Executable="$targetnametoken$.exe"
|
Executable="$targetnametoken$.exe"
|
||||||
EntryPoint="$targetentrypoint$"
|
EntryPoint="$targetentrypoint$"
|
||||||
uap10:Parameters="--mode=mail">
|
uap10:Parameters="--wino-mail">
|
||||||
<uap:VisualElements
|
<uap:VisualElements
|
||||||
DisplayName="Wino Mail"
|
DisplayName="Wino Mail"
|
||||||
Description="Wino.Mail.WinUI"
|
Description="Wino.Mail.WinUI"
|
||||||
@@ -97,7 +97,45 @@
|
|||||||
</uap:Protocol>
|
</uap:Protocol>
|
||||||
</uap:Extension>
|
</uap:Extension>
|
||||||
|
|
||||||
<!-- Protocol activation: webcal -->
|
<!-- File Assosication: EML -->
|
||||||
|
<uap:Extension Category="windows.fileTypeAssociation">
|
||||||
|
<uap:FileTypeAssociation Name="eml">
|
||||||
|
<uap:Logo>EML\eml.png</uap:Logo>
|
||||||
|
<uap:SupportedFileTypes>
|
||||||
|
<uap:FileType>.eml</uap:FileType>
|
||||||
|
</uap:SupportedFileTypes>
|
||||||
|
</uap:FileTypeAssociation>
|
||||||
|
</uap:Extension>
|
||||||
|
</Extensions>
|
||||||
|
</Application>
|
||||||
|
|
||||||
|
<Application Id="CalendarApp"
|
||||||
|
Executable="$targetnametoken$.exe"
|
||||||
|
EntryPoint="$targetentrypoint$"
|
||||||
|
uap10:Parameters="--wino-calendar">
|
||||||
|
<uap:VisualElements
|
||||||
|
DisplayName="Wino Calendar"
|
||||||
|
Description="Wino.Mail.WinUI.Calendar"
|
||||||
|
BackgroundColor="transparent"
|
||||||
|
Square150x150Logo="Assets\AppEntries\CalendarAssets\Square150x150Logo.png"
|
||||||
|
Square44x44Logo="Assets\AppEntries\CalendarAssets\Square44x44Logo.png">
|
||||||
|
<uap:DefaultTile Wide310x150Logo="Assets\AppEntries\CalendarAssets\Wide310x150Logo.png" Square71x71Logo="Assets\AppEntries\CalendarAssets\SmallTile.png" Square310x310Logo="Assets\AppEntries\CalendarAssets\LargeTile.png"/>
|
||||||
|
<uap:SplashScreen Image="Assets\AppEntries\CalendarAssets\SplashScreen.png" />
|
||||||
|
</uap:VisualElements>
|
||||||
|
|
||||||
|
<Extensions>
|
||||||
|
<desktop:Extension Category="windows.toastNotificationActivation">
|
||||||
|
<desktop:ToastNotificationActivation ToastActivatorCLSID="44c05d2b-aa1d-4e59-9d7d-8b4c8607cb8d" />
|
||||||
|
</desktop:Extension>
|
||||||
|
|
||||||
|
<com:Extension Category="windows.comServer">
|
||||||
|
<com:ComServer>
|
||||||
|
<com:ExeServer Executable="Wino.Mail.WinUI.exe" Arguments="----AppNotificationActivated:" DisplayName="Calendar toast activator">
|
||||||
|
<com:Class Id="44c05d2b-aa1d-4e59-9d7d-8b4c8607cb8d" DisplayName="Calendar toast activator"/>
|
||||||
|
</com:ExeServer>
|
||||||
|
</com:ComServer>
|
||||||
|
</com:Extension>
|
||||||
|
|
||||||
<uap:Extension Category="windows.protocol">
|
<uap:Extension Category="windows.protocol">
|
||||||
<uap:Protocol Name="webcal">
|
<uap:Protocol Name="webcal">
|
||||||
<uap:DisplayName>Calendar Protocol</uap:DisplayName>
|
<uap:DisplayName>Calendar Protocol</uap:DisplayName>
|
||||||
@@ -110,17 +148,6 @@
|
|||||||
</uap:Protocol>
|
</uap:Protocol>
|
||||||
</uap:Extension>
|
</uap:Extension>
|
||||||
|
|
||||||
<!-- File Assosication: EML -->
|
|
||||||
<uap:Extension Category="windows.fileTypeAssociation">
|
|
||||||
<uap:FileTypeAssociation Name="eml">
|
|
||||||
<uap:Logo>EML\eml.png</uap:Logo>
|
|
||||||
<uap:SupportedFileTypes>
|
|
||||||
<uap:FileType>.eml</uap:FileType>
|
|
||||||
</uap:SupportedFileTypes>
|
|
||||||
</uap:FileTypeAssociation>
|
|
||||||
</uap:Extension>
|
|
||||||
|
|
||||||
<!-- File Association: ICS -->
|
|
||||||
<uap:Extension Category="windows.fileTypeAssociation">
|
<uap:Extension Category="windows.fileTypeAssociation">
|
||||||
<uap:FileTypeAssociation Name="ics">
|
<uap:FileTypeAssociation Name="ics">
|
||||||
<uap:Logo>Assets\AppEntries\CalendarAssets\Square44x44Logo.png</uap:Logo>
|
<uap:Logo>Assets\AppEntries\CalendarAssets\Square44x44Logo.png</uap:Logo>
|
||||||
|
|||||||
+10
-20
@@ -7,8 +7,7 @@ using Microsoft.UI.Dispatching;
|
|||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.Windows.AppNotifications;
|
using Microsoft.Windows.AppNotifications;
|
||||||
using Microsoft.Windows.AppLifecycle;
|
using Microsoft.Windows.AppLifecycle;
|
||||||
using Wino.Core.Domain;
|
using Wino.Mail.WinUI.Activation;
|
||||||
using Wino.Core.Domain.Enums;
|
|
||||||
|
|
||||||
namespace Wino.Mail.WinUI;
|
namespace Wino.Mail.WinUI;
|
||||||
|
|
||||||
@@ -200,28 +199,19 @@ public class Program
|
|||||||
|
|
||||||
private static bool ShouldBringWindowToForegroundAfterRedirection(AppActivationArguments args)
|
private static bool ShouldBringWindowToForegroundAfterRedirection(AppActivationArguments args)
|
||||||
{
|
{
|
||||||
if (args.Kind != ExtendedActivationKind.AppNotification ||
|
if (args.Kind == ExtendedActivationKind.AppNotification &&
|
||||||
args.Data is not AppNotificationActivatedEventArgs toastArgs)
|
args.Data is AppNotificationActivatedEventArgs toastArgs)
|
||||||
{
|
{
|
||||||
return true;
|
return ToastActivationResolver.TryParse(toastArgs.Argument, out var toastArguments)
|
||||||
|
? ToastActivationResolver.ShouldBringToForeground(toastArguments)
|
||||||
|
: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var toastArguments = NotificationArguments.Parse(toastArgs.Argument);
|
if (args.Kind == ExtendedActivationKind.Launch &&
|
||||||
|
args.Data is Windows.ApplicationModel.Activation.ILaunchActivatedEventArgs launchArgs &&
|
||||||
if (toastArguments.TryGetValue(Constants.ToastStoreUpdateActionKey, out string storeUpdateAction) &&
|
ToastActivationResolver.TryParse(launchArgs.Arguments, out var launchToastArguments))
|
||||||
storeUpdateAction == Constants.ToastStoreUpdateActionInstall)
|
|
||||||
{
|
{
|
||||||
return true;
|
return ToastActivationResolver.ShouldBringToForeground(launchToastArguments);
|
||||||
}
|
|
||||||
|
|
||||||
if (toastArguments.TryGetValue(Constants.ToastCalendarActionKey, out string calendarAction))
|
|
||||||
{
|
|
||||||
return calendarAction == Constants.ToastCalendarNavigateAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toastArguments.TryGetValue(Constants.ToastActionKey, out MailOperation mailAction))
|
|
||||||
{
|
|
||||||
return mailAction == MailOperation.Navigate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Microsoft.Windows.AppNotifications;
|
using CommunityToolkit.WinUI.Notifications;
|
||||||
using Microsoft.Windows.AppNotifications.Builder;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Windows.Data.Xml.Dom;
|
using Windows.Data.Xml.Dom;
|
||||||
|
using Windows.Storage;
|
||||||
using Windows.UI.Notifications;
|
using Windows.UI.Notifications;
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
using Wino.Core.Domain.Entities.Calendar;
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
@@ -16,14 +17,15 @@ using Wino.Core.Domain.Entities.Shared;
|
|||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Extensions;
|
using Wino.Core.Domain.Extensions;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Mail.WinUI.Activation;
|
||||||
using Wino.Messaging.UI;
|
using Wino.Messaging.UI;
|
||||||
|
|
||||||
namespace Wino.Mail.WinUI.Services;
|
namespace Wino.Mail.WinUI.Services;
|
||||||
|
|
||||||
public class NotificationBuilder : INotificationBuilder
|
public class NotificationBuilder : INotificationBuilder
|
||||||
{
|
{
|
||||||
private const string MailApplicationId = "App";
|
|
||||||
private const string NotificationIconRootUri = "ms-appx:///Assets/NotificationIcons/";
|
private const string NotificationIconRootUri = "ms-appx:///Assets/NotificationIcons/";
|
||||||
|
private static int _calendarTaskbarBadgeCount;
|
||||||
|
|
||||||
private readonly IAccountService _accountService;
|
private readonly IAccountService _accountService;
|
||||||
private readonly IFolderService _folderService;
|
private readonly IFolderService _folderService;
|
||||||
@@ -53,50 +55,33 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Filter mails to only include Inbox folder items
|
|
||||||
var inboxMailItems = new List<MailCopy>();
|
var inboxMailItems = new List<MailCopy>();
|
||||||
|
|
||||||
foreach (var item in downloadedMailItems)
|
foreach (var item in downloadedMailItems)
|
||||||
{
|
{
|
||||||
var mailItem = await _mailService.GetSingleMailItemAsync(item.UniqueId);
|
var mailItem = await _mailService.GetSingleMailItemAsync(item.UniqueId);
|
||||||
|
if (mailItem != null)
|
||||||
//if (mailItem == null || mailItem.AssignedFolder == null)
|
{
|
||||||
// continue;
|
|
||||||
|
|
||||||
//// Only create notifications for Inbox folder mails
|
|
||||||
//if (mailItem.AssignedFolder.SpecialFolderType != SpecialFolderType.Inbox)
|
|
||||||
// continue;
|
|
||||||
|
|
||||||
//// Skip folders with synchronization disabled
|
|
||||||
//if (!mailItem.AssignedFolder.IsSynchronizationEnabled)
|
|
||||||
// continue;
|
|
||||||
|
|
||||||
//// Skip already read mails
|
|
||||||
//if (mailItem.IsRead)
|
|
||||||
//{
|
|
||||||
// RemoveNotification(mailItem.UniqueId);
|
|
||||||
// continue;
|
|
||||||
//}
|
|
||||||
|
|
||||||
inboxMailItems.Add(mailItem);
|
inboxMailItems.Add(mailItem);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var mailCount = inboxMailItems.Count;
|
var mailCount = inboxMailItems.Count;
|
||||||
|
|
||||||
if (mailCount == 0)
|
if (mailCount == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// If there are more than 3 mails, just display 1 general notification.
|
|
||||||
if (mailCount > 3)
|
if (mailCount > 3)
|
||||||
{
|
{
|
||||||
var builder = CreateBuilder();
|
var builder = new ToastContentBuilder()
|
||||||
|
.AddText(Translator.Notifications_MultipleNotificationsTitle)
|
||||||
|
.AddText(string.Format(Translator.Notifications_MultipleNotificationsMessage, mailCount))
|
||||||
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail)
|
||||||
|
.AddAudio(new ToastAudio
|
||||||
|
{
|
||||||
|
Src = new Uri("ms-winsoundevent:Notification.Mail")
|
||||||
|
});
|
||||||
|
|
||||||
builder.AddText(Translator.Notifications_MultipleNotificationsTitle);
|
ShowToast(builder, ToastTargetApp.Mail);
|
||||||
builder.AddText(string.Format(Translator.Notifications_MultipleNotificationsMessage, mailCount));
|
|
||||||
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
|
||||||
builder.SetAudioUri(new Uri("ms-winsoundevent:Notification.Mail"));
|
|
||||||
|
|
||||||
ShowNotification(builder);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -114,104 +99,28 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CreateSingleNotificationAsync(MailCopy mailItem)
|
|
||||||
{
|
|
||||||
var builder = CreateBuilder();
|
|
||||||
|
|
||||||
var avatarThumbnail = await _thumbnailService.GetThumbnailAsync(mailItem.FromAddress, awaitLoad: true);
|
|
||||||
if (!string.IsNullOrEmpty(avatarThumbnail))
|
|
||||||
{
|
|
||||||
var tempFile = await Windows.Storage.ApplicationData.Current.TemporaryFolder.CreateFileAsync($"{Guid.NewGuid()}.png", Windows.Storage.CreationCollisionOption.ReplaceExisting);
|
|
||||||
await using (var stream = await tempFile.OpenStreamForWriteAsync())
|
|
||||||
{
|
|
||||||
var bytes = Convert.FromBase64String(avatarThumbnail);
|
|
||||||
await stream.WriteAsync(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.SetAppLogoOverride(new Uri($"ms-appdata:///temp/{tempFile.Name}"), AppNotificationImageCrop.Default);
|
|
||||||
}
|
|
||||||
|
|
||||||
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.ToString());
|
|
||||||
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
|
||||||
|
|
||||||
builder.AddButton(GetMarkAsReadButton(mailItem.UniqueId));
|
|
||||||
builder.AddButton(GetDeleteButton(mailItem.UniqueId));
|
|
||||||
builder.AddButton(GetArchiveButton(mailItem.UniqueId));
|
|
||||||
builder.SetAudioUri(new Uri("ms-winsoundevent:Notification.Mail"));
|
|
||||||
|
|
||||||
ShowNotification(builder, mailItem.UniqueId.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private AppNotificationButton GetArchiveButton(Guid mailUniqueId)
|
|
||||||
=> new AppNotificationButton(Translator.MailOperation_Archive)
|
|
||||||
.SetIcon(GetNotificationIconUri("mail-archive"))
|
|
||||||
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
|
||||||
.AddArgument(Constants.ToastActionKey, MailOperation.Archive.ToString())
|
|
||||||
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
|
||||||
|
|
||||||
private AppNotificationButton GetDeleteButton(Guid mailUniqueId)
|
|
||||||
=> new AppNotificationButton(Translator.MailOperation_Delete)
|
|
||||||
.SetIcon(GetNotificationIconUri("mail-delete"))
|
|
||||||
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
|
||||||
.AddArgument(Constants.ToastActionKey, MailOperation.SoftDelete.ToString())
|
|
||||||
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
|
||||||
|
|
||||||
private AppNotificationButton GetMarkAsReadButton(Guid mailUniqueId)
|
|
||||||
=> new AppNotificationButton(Translator.MailOperation_MarkAsRead)
|
|
||||||
.SetIcon(GetNotificationIconUri("mail-markread"))
|
|
||||||
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
|
||||||
.AddArgument(Constants.ToastActionKey, MailOperation.MarkAsRead.ToString())
|
|
||||||
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
|
||||||
|
|
||||||
public async Task UpdateTaskbarIconBadgeAsync()
|
public async Task UpdateTaskbarIconBadgeAsync()
|
||||||
{
|
{
|
||||||
int totalUnreadCount = 0;
|
var totalUnreadCount = 0;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var badgeUpdater = BadgeUpdateManager.CreateBadgeUpdaterForApplication(MailApplicationId);
|
|
||||||
var accounts = await _accountService.GetAccountsAsync();
|
var accounts = await _accountService.GetAccountsAsync();
|
||||||
|
|
||||||
foreach (var account in accounts)
|
foreach (var account in accounts)
|
||||||
{
|
{
|
||||||
if (!account.Preferences.IsTaskbarBadgeEnabled) continue;
|
if (!account.Preferences.IsTaskbarBadgeEnabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
var accountInbox = await _folderService.GetSpecialFolderByAccountIdAsync(account.Id, SpecialFolderType.Inbox);
|
var accountInbox = await _folderService.GetSpecialFolderByAccountIdAsync(account.Id, SpecialFolderType.Inbox);
|
||||||
|
|
||||||
if (accountInbox == null)
|
if (accountInbox == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var inboxUnreadCount = await _folderService.GetFolderNotificationBadgeAsync(accountInbox.Id);
|
var inboxUnreadCount = await _folderService.GetFolderNotificationBadgeAsync(accountInbox.Id);
|
||||||
|
|
||||||
totalUnreadCount += inboxUnreadCount;
|
totalUnreadCount += inboxUnreadCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalUnreadCount > 0)
|
UpdateBadge(AppEntryConstants.MailApplicationId, totalUnreadCount > 0 ? totalUnreadCount : null);
|
||||||
{
|
|
||||||
XmlDocument badgeXml = BadgeUpdateManager.GetTemplateContent(BadgeTemplateType.BadgeNumber);
|
|
||||||
|
|
||||||
XmlElement? badgeElement = badgeXml.SelectSingleNode("/badge") as XmlElement;
|
|
||||||
if (badgeElement == null)
|
|
||||||
{
|
|
||||||
badgeUpdater.Clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
badgeElement.SetAttribute("value", totalUnreadCount.ToString());
|
|
||||||
|
|
||||||
BadgeNotification badge = new(badgeXml);
|
|
||||||
badgeUpdater.Update(badge);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
badgeUpdater.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -219,15 +128,34 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task AddCalendarTaskbarBadgeCountAsync(int newlyDownloadedCount)
|
||||||
|
{
|
||||||
|
if (newlyDownloadedCount <= 0)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
var badgeCount = Interlocked.Add(ref _calendarTaskbarBadgeCount, newlyDownloadedCount);
|
||||||
|
UpdateBadge(AppEntryConstants.CalendarApplicationId, badgeCount > 0 ? badgeCount : null);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ClearCalendarTaskbarBadgeAsync()
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _calendarTaskbarBadgeCount, 0);
|
||||||
|
UpdateBadge(AppEntryConstants.CalendarApplicationId, null);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
public void RemoveNotification(Guid mailUniqueId)
|
public void RemoveNotification(Guid mailUniqueId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
AppNotificationManager.Default.RemoveByTagAsync(mailUniqueId.ToString()).AsTask().GetAwaiter().GetResult();
|
ToastNotificationManager.History.Remove(
|
||||||
|
mailUniqueId.ToString(),
|
||||||
|
null,
|
||||||
|
AppEntryConstants.GetAppUserModelId(WinoApplicationMode.Mail));
|
||||||
}
|
}
|
||||||
catch (ArgumentException)
|
catch (ArgumentException)
|
||||||
{
|
{
|
||||||
// Notification does not exists. Ignore.
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -237,39 +165,38 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
|
|
||||||
public void CreateAttentionRequiredNotification(MailAccount account)
|
public void CreateAttentionRequiredNotification(MailAccount account)
|
||||||
{
|
{
|
||||||
var builder = CreateBuilder();
|
var builder = new ToastContentBuilder()
|
||||||
|
.AddText(Translator.Exception_AccountNeedsAttention_Title)
|
||||||
builder.AddText(Translator.Exception_AccountNeedsAttention_Title);
|
.AddText(string.Format(Translator.Exception_AccountNeedsAttention_Message, account.Name))
|
||||||
builder.AddText(string.Format(Translator.Exception_AccountNeedsAttention_Message, account.Name));
|
.AddArgument(Constants.ToastMailAccountIdKey, account.Id.ToString())
|
||||||
builder.AddArgument(Constants.ToastMailAccountIdKey, account.Id.ToString());
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail)
|
||||||
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
.AddButton(new ToastButton()
|
||||||
builder.AddButton(new AppNotificationButton(Translator.Buttons_FixAccount)
|
.SetContent(Translator.Buttons_FixAccount)
|
||||||
.AddArgument(Constants.ToastMailAccountIdKey, account.Id.ToString())
|
.AddArgument(Constants.ToastMailAccountIdKey, account.Id.ToString())
|
||||||
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail));
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail));
|
||||||
|
|
||||||
ShowNotification(builder);
|
ShowToast(builder, ToastTargetApp.Mail);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateWebView2RuntimeMissingNotification()
|
public void CreateWebView2RuntimeMissingNotification()
|
||||||
{
|
{
|
||||||
var builder = CreateBuilder();
|
var builder = new ToastContentBuilder()
|
||||||
|
.AddText(Translator.Exception_WebView2RuntimeMissing_Title)
|
||||||
|
.AddText(Translator.Exception_WebView2RuntimeMissing_Message)
|
||||||
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
||||||
|
|
||||||
builder.AddText(Translator.Exception_WebView2RuntimeMissing_Title);
|
ShowToast(builder, ToastTargetApp.Mail);
|
||||||
builder.AddText(Translator.Exception_WebView2RuntimeMissing_Message);
|
|
||||||
builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
|
||||||
|
|
||||||
ShowNotification(builder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateStoreUpdateNotification()
|
public void CreateStoreUpdateNotification()
|
||||||
{
|
{
|
||||||
var builder = CreateBuilder();
|
var builder = new ToastContentBuilder()
|
||||||
|
.AddText(Translator.Notifications_StoreUpdateAvailableTitle)
|
||||||
|
.AddText(Translator.Notifications_StoreUpdateAvailableMessage)
|
||||||
|
.AddArgument(Constants.ToastStoreUpdateActionKey, Constants.ToastStoreUpdateActionInstall)
|
||||||
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
||||||
|
|
||||||
builder.AddText(Translator.Notifications_StoreUpdateAvailableTitle);
|
ShowToast(builder, ToastTargetApp.Mail, "store-update-available");
|
||||||
builder.AddText(Translator.Notifications_StoreUpdateAvailableMessage);
|
|
||||||
builder.AddArgument(Constants.ToastStoreUpdateActionKey, Constants.ToastStoreUpdateActionInstall);
|
|
||||||
|
|
||||||
ShowNotification(builder, "store-update-available");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task CreateCalendarReminderNotificationAsync(CalendarItem calendarItem, long reminderDurationInSeconds)
|
public Task CreateCalendarReminderNotificationAsync(CalendarItem calendarItem, long reminderDurationInSeconds)
|
||||||
@@ -277,11 +204,10 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
if (calendarItem == null)
|
if (calendarItem == null)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
var builder = CreateBuilder(AppNotificationScenario.Reminder);
|
var builder = new ToastContentBuilder()
|
||||||
|
.SetToastScenario(ToastScenario.Reminder);
|
||||||
var localStart = calendarItem.GetLocalStartDate();
|
var localStart = calendarItem.GetLocalStartDate();
|
||||||
var nowLocal = DateTime.Now;
|
var reminderContext = GetCalendarReminderContext(localStart, DateTime.Now);
|
||||||
var reminderContext = GetCalendarReminderContext(localStart, nowLocal);
|
|
||||||
|
|
||||||
builder.AddText(calendarItem.Title);
|
builder.AddText(calendarItem.Title);
|
||||||
builder.AddText($"{reminderContext} - {localStart:g}");
|
builder.AddText($"{reminderContext} - {localStart:g}");
|
||||||
@@ -292,6 +218,10 @@ 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.AddAudio(new ToastAudio
|
||||||
|
{
|
||||||
|
Src = new Uri("ms-winsoundevent:Notification.Reminder")
|
||||||
|
});
|
||||||
|
|
||||||
var allowedSnoozeMinutes = CalendarReminderSnoozeOptions.GetAllowedSnoozeMinutes(
|
var allowedSnoozeMinutes = CalendarReminderSnoozeOptions.GetAllowedSnoozeMinutes(
|
||||||
reminderDurationInSeconds,
|
reminderDurationInSeconds,
|
||||||
@@ -304,42 +234,108 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
? preferredSnoozeMinutes
|
? preferredSnoozeMinutes
|
||||||
: allowedSnoozeMinutes[0];
|
: allowedSnoozeMinutes[0];
|
||||||
|
|
||||||
var selectionBox = new AppNotificationComboBox(Constants.ToastCalendarSnoozeDurationInputId)
|
var selectionBox = new ToastSelectionBox(Constants.ToastCalendarSnoozeDurationInputId)
|
||||||
.SetSelectedItem(defaultSnoozeMinutes.ToString());
|
{
|
||||||
|
DefaultSelectionBoxItemId = defaultSnoozeMinutes.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
foreach (var snoozeMinutes in allowedSnoozeMinutes)
|
foreach (var snoozeMinutes in allowedSnoozeMinutes)
|
||||||
{
|
{
|
||||||
selectionBox.AddItem(
|
selectionBox.Items.Add(new ToastSelectionBoxItem(
|
||||||
snoozeMinutes.ToString(),
|
snoozeMinutes.ToString(),
|
||||||
string.Format(Translator.CalendarReminder_SnoozeMinutesOption, snoozeMinutes));
|
string.Format(Translator.CalendarReminder_SnoozeMinutesOption, snoozeMinutes)));
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AddComboBox(selectionBox);
|
builder.AddToastInput(selectionBox);
|
||||||
builder.AddButton(new AppNotificationButton(Translator.CalendarReminder_SnoozeAction)
|
builder.AddButton(new ToastButton()
|
||||||
|
.SetContent(Translator.CalendarReminder_SnoozeAction)
|
||||||
|
.SetImageUri(GetNotificationIconUri("calendar-snooze"))
|
||||||
|
.SetBackgroundActivation()
|
||||||
.AddArgument(Constants.ToastCalendarActionKey, Constants.ToastCalendarSnoozeAction)
|
.AddArgument(Constants.ToastCalendarActionKey, Constants.ToastCalendarSnoozeAction)
|
||||||
.AddArgument(Constants.ToastCalendarItemIdKey, calendarItem.Id.ToString())
|
.AddArgument(Constants.ToastCalendarItemIdKey, calendarItem.Id.ToString())
|
||||||
.AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar));
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar));
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AddButton(new AppNotificationButton(Translator.Buttons_Open)
|
builder.AddButton(new ToastButton()
|
||||||
|
.SetContent(Translator.Buttons_Open)
|
||||||
|
.SetBackgroundActivation()
|
||||||
.AddArgument(Constants.ToastCalendarActionKey, Constants.ToastCalendarNavigateAction)
|
.AddArgument(Constants.ToastCalendarActionKey, Constants.ToastCalendarNavigateAction)
|
||||||
.AddArgument(Constants.ToastCalendarItemIdKey, calendarItem.Id.ToString())
|
.AddArgument(Constants.ToastCalendarItemIdKey, calendarItem.Id.ToString())
|
||||||
.AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar));
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeCalendar));
|
||||||
|
|
||||||
if (Uri.TryCreate(calendarItem.HtmlLink, UriKind.Absolute, out var joinUri))
|
if (Uri.TryCreate(calendarItem.HtmlLink, UriKind.Absolute, out var joinUri))
|
||||||
{
|
{
|
||||||
builder.AddButton(new AppNotificationButton(Translator.CalendarEventDetails_JoinOnline)
|
builder.AddButton(new ToastButton()
|
||||||
.SetInvokeUri(joinUri));
|
.SetContent(Translator.CalendarEventDetails_JoinOnline)
|
||||||
|
.SetImageUri(GetNotificationIconUri("calendar-join"))
|
||||||
|
.SetProtocolActivation(joinUri));
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.SetAudioUri(new Uri("ms-winsoundevent:Notification.Reminder"));
|
|
||||||
|
|
||||||
var tag = $"calendar-reminder-{calendarItem.Id:N}-{reminderDurationInSeconds}";
|
var tag = $"calendar-reminder-{calendarItem.Id:N}-{reminderDurationInSeconds}";
|
||||||
ShowNotification(builder, tag);
|
ShowToast(builder, ToastTargetApp.Calendar, tag);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task CreateSingleNotificationAsync(MailCopy mailItem)
|
||||||
|
{
|
||||||
|
var builder = new ToastContentBuilder();
|
||||||
|
|
||||||
|
var avatarThumbnail = await _thumbnailService.GetThumbnailAsync(mailItem.FromAddress, awaitLoad: true);
|
||||||
|
if (!string.IsNullOrEmpty(avatarThumbnail))
|
||||||
|
{
|
||||||
|
var tempFile = await Windows.Storage.ApplicationData.Current.TemporaryFolder.CreateFileAsync(
|
||||||
|
$"{Guid.NewGuid()}.png",
|
||||||
|
Windows.Storage.CreationCollisionOption.ReplaceExisting);
|
||||||
|
|
||||||
|
await using (var stream = await tempFile.OpenStreamForWriteAsync())
|
||||||
|
{
|
||||||
|
var bytes = Convert.FromBase64String(avatarThumbnail);
|
||||||
|
await stream.WriteAsync(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AddAppLogoOverride(new Uri($"ms-appdata:///temp/{tempFile.Name}"), ToastGenericAppLogoCrop.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AddCustomTimeStamp(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.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")
|
||||||
|
});
|
||||||
|
|
||||||
|
ShowToast(builder, ToastTargetApp.Mail, mailItem.UniqueId.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateBadge(string applicationId, int? badgeCount)
|
||||||
|
{
|
||||||
|
var badgeUpdater = BadgeUpdateManager.CreateBadgeUpdaterForApplication(applicationId);
|
||||||
|
|
||||||
|
if (!badgeCount.HasValue || badgeCount.Value <= 0)
|
||||||
|
{
|
||||||
|
badgeUpdater.Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
XmlDocument badgeXml = BadgeUpdateManager.GetTemplateContent(BadgeTemplateType.BadgeNumber);
|
||||||
|
if (badgeXml.SelectSingleNode("/badge") is not XmlElement badgeElement)
|
||||||
|
{
|
||||||
|
badgeUpdater.Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
badgeElement.SetAttribute("value", badgeCount.Value.ToString());
|
||||||
|
badgeUpdater.Update(new BadgeNotification(badgeXml));
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetCalendarReminderContext(DateTime localStart, DateTime nowLocal)
|
private static string GetCalendarReminderContext(DateTime localStart, DateTime nowLocal)
|
||||||
{
|
{
|
||||||
var delta = localStart - nowLocal;
|
var delta = localStart - nowLocal;
|
||||||
@@ -370,22 +366,55 @@ public class NotificationBuilder : INotificationBuilder
|
|||||||
return string.Format(Translator.CalendarReminder_StartedMinutesAgo, minutesAgo);
|
return string.Format(Translator.CalendarReminder_StartedMinutesAgo, minutesAgo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AppNotificationBuilder CreateBuilder(AppNotificationScenario scenario = AppNotificationScenario.Default)
|
private ToastButton GetArchiveButton(Guid mailUniqueId)
|
||||||
=> new AppNotificationBuilder().SetScenario(scenario);
|
=> new ToastButton()
|
||||||
|
.SetContent(Translator.MailOperation_Archive)
|
||||||
|
.SetImageUri(GetNotificationIconUri("mail-archive"))
|
||||||
|
.SetBackgroundActivation()
|
||||||
|
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
||||||
|
.AddArgument(Constants.ToastActionKey, MailOperation.Archive)
|
||||||
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
||||||
|
|
||||||
private static void ShowNotification(AppNotificationBuilder builder, string? tag = null)
|
private ToastButton GetDeleteButton(Guid mailUniqueId)
|
||||||
|
=> new ToastButton()
|
||||||
|
.SetContent(Translator.MailOperation_Delete)
|
||||||
|
.SetImageUri(GetNotificationIconUri("mail-delete"))
|
||||||
|
.SetBackgroundActivation()
|
||||||
|
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
||||||
|
.AddArgument(Constants.ToastActionKey, MailOperation.SoftDelete)
|
||||||
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
||||||
|
|
||||||
|
private ToastButton GetMarkAsReadButton(Guid mailUniqueId)
|
||||||
|
=> new ToastButton()
|
||||||
|
.SetContent(Translator.MailOperation_MarkAsRead)
|
||||||
|
.SetImageUri(GetNotificationIconUri("mail-markread"))
|
||||||
|
.SetBackgroundActivation()
|
||||||
|
.AddArgument(Constants.ToastMailUniqueIdKey, mailUniqueId.ToString())
|
||||||
|
.AddArgument(Constants.ToastActionKey, MailOperation.MarkAsRead)
|
||||||
|
.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
|
||||||
|
|
||||||
|
private static void ShowToast(ToastContentBuilder builder, ToastTargetApp targetApp, string? tag = null)
|
||||||
{
|
{
|
||||||
var notification = builder.BuildNotification();
|
var toastNotification = new ToastNotification(builder.GetToastContent().GetXml());
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(tag))
|
if (!string.IsNullOrWhiteSpace(tag))
|
||||||
notification.Tag = tag;
|
|
||||||
|
|
||||||
AppNotificationManager.Default.Show(notification);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Uri GetNotificationIconUri(string iconName)
|
|
||||||
{
|
{
|
||||||
// Keep the URI unqualified so Windows resolves the best matching theme/scale asset from the package.
|
toastNotification.Tag = tag;
|
||||||
return new($"{NotificationIconRootUri}{iconName}.png");
|
}
|
||||||
|
|
||||||
|
ToastNotificationManager
|
||||||
|
.CreateToastNotifier(AppEntryConstants.GetAppUserModelId(targetApp == ToastTargetApp.Calendar
|
||||||
|
? WinoApplicationMode.Calendar
|
||||||
|
: WinoApplicationMode.Mail))
|
||||||
|
.Show(toastNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Uri GetNotificationIconUri(string iconName)
|
||||||
|
=> new($"{NotificationIconRootUri}{iconName}.png");
|
||||||
|
|
||||||
|
private enum ToastTargetApp
|
||||||
|
{
|
||||||
|
Mail,
|
||||||
|
Calendar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Windows.ApplicationModel;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
using Wino.Mail.WinUI.Activation;
|
||||||
|
|
||||||
|
namespace Wino.Mail.WinUI.Services;
|
||||||
|
|
||||||
|
internal sealed class PackagedAppEntryLauncher
|
||||||
|
{
|
||||||
|
public async Task<bool> LaunchAsync(WinoApplicationMode mode)
|
||||||
|
{
|
||||||
|
var targetApplicationId = AppEntryConstants.GetPackagedApplicationId(mode);
|
||||||
|
if (string.IsNullOrWhiteSpace(targetApplicationId))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var targetAppUserModelId = AppEntryConstants.GetAppUserModelId(mode);
|
||||||
|
var appEntries = await Package.Current.GetAppListEntriesAsync();
|
||||||
|
var appEntry = appEntries.FirstOrDefault(entry =>
|
||||||
|
string.Equals(entry.AppUserModelId, targetAppUserModelId, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
return appEntry != null && await appEntry.LaunchAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -117,6 +117,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
|
|||||||
public void HandleAppActivation(string? launchArguments, string? tileId = null, string? appId = null)
|
public void HandleAppActivation(string? launchArguments, string? tileId = null, string? appId = null)
|
||||||
{
|
{
|
||||||
var targetMode = AppModeActivationResolver.Resolve(launchArguments, tileId, appId, PreferencesService.DefaultApplicationMode);
|
var targetMode = AppModeActivationResolver.Resolve(launchArguments, tileId, appId, PreferencesService.DefaultApplicationMode);
|
||||||
|
WindowAppUserModelIdHelper.TrySet(this, AppEntryConstants.GetAppUserModelId(targetMode));
|
||||||
NavigationService.ChangeApplicationMode(targetMode);
|
NavigationService.ChangeApplicationMode(targetMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -174,6 +174,7 @@
|
|||||||
<PackageReference Include="CommunityToolkit.Diagnostics" />
|
<PackageReference Include="CommunityToolkit.Diagnostics" />
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" />
|
<PackageReference Include="CommunityToolkit.Mvvm" />
|
||||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.DependencyPropertyGenerator" />
|
<PackageReference Include="CommunityToolkit.Labs.WinUI.DependencyPropertyGenerator" />
|
||||||
|
<PackageReference Include="CommunityToolkit.WinUI.Notifications" />
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.Animations" />
|
<PackageReference Include="CommunityToolkit.WinUI.Animations" />
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" />
|
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" />
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" />
|
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" />
|
||||||
|
|||||||
Reference in New Issue
Block a user