Fix notification activation and calendar bootstrap flow

This commit is contained in:
Burak Kaan Köse
2026-04-16 01:32:48 +02:00
parent 94675eee9a
commit e13aaadc78
15 changed files with 844 additions and 209 deletions
+375 -161
View File
@@ -36,6 +36,7 @@ using Wino.Mail.ViewModels;
using Wino.Mail.ViewModels.Data;
using Wino.Mail.WinUI.Activation;
using Wino.Mail.WinUI.Extensions;
using Wino.Mail.WinUI.Helpers;
using Wino.Mail.WinUI.Interfaces;
using Wino.Mail.WinUI.Models;
using Wino.Mail.WinUI.Services;
@@ -66,13 +67,19 @@ public partial class App : WinoApplication,
private bool _hasConfiguredAccounts;
private bool _isExiting;
private bool _activationInfrastructureInitialized;
private bool _appHostInfrastructureInitialized;
private bool _appNotificationsRegistered;
private int _initialNotificationActivationHandled;
private int _initialShareActivationHandled;
private CancellationTokenSource? _autoSynchronizationLoopCts;
private readonly SemaphoreSlim _autoSynchronizationSemaphore = new(1, 1);
private readonly SemaphoreSlim _activationInfrastructureSemaphore = new(1, 1);
private readonly SemaphoreSlim _appHostInfrastructureSemaphore = new(1, 1);
private readonly ConcurrentDictionary<Guid, int> _inboxSyncCounters = [];
private readonly AppNotificationActivationBuffer _bufferedAppNotificationActivations = new();
private NativeTrayIcon? _trayIcon;
private readonly record struct NotificationActivationRoute(bool RequiresForegroundWindow, Func<Task>? ExecuteAsync);
private readonly record struct ShellWindowActivationResult(IWinoShellWindow? ShellWindow, bool WasCreated);
internal bool IsExiting => _isExiting;
@@ -83,6 +90,7 @@ public partial class App : WinoApplication,
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
CryptographyContext.Register(typeof(WindowsSecureMimeContext));
EnsureAppNotificationRegistration();
RegisterRecipients();
}
@@ -271,12 +279,16 @@ public partial class App : WinoApplication,
shellWindow.Close();
}
private async Task ActivateWindowAsync(WindowEx window)
private async Task ActivateWindowAsync(WindowEx window, bool applyThemeToWindow = true)
{
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
MainWindow = window;
windowManager.ActivateWindow(window);
await NewThemeService.ApplyThemeToActiveWindowAsync();
if (applyThemeToWindow)
{
await NewThemeService.ApplyThemeToActiveWindowAsync();
}
}
private Task ExitApplicationAsync()
@@ -410,6 +422,12 @@ public partial class App : WinoApplication,
=> Services.GetRequiredService<IWinoWindowManager>().GetWindow(WinoWindowKind.Shell) is IWinoShellWindow;
private async Task EnsureActivationInfrastructureAsync()
{
await EnsureCoreActivationInfrastructureAsync();
await EnsureAppHostInfrastructureAsync();
}
private async Task EnsureCoreActivationInfrastructureAsync()
{
if (_activationInfrastructureInitialized)
return;
@@ -421,7 +439,7 @@ public partial class App : WinoApplication,
if (_activationInfrastructureInitialized)
return;
TryRegisterAppNotifications();
EnsureAppNotificationRegistration();
await Services.GetRequiredService<ReleaseLocalAccountDataCleanupService>()
.RunIfNeededAsync();
@@ -432,18 +450,8 @@ public partial class App : WinoApplication,
_preferencesService = Services.GetRequiredService<IPreferencesService>();
_accountService = Services.GetRequiredService<IAccountService>();
EnsureWindowManagerConfigured();
EnsureTrayIconCreated();
_hasConfiguredAccounts = (await _accountService.GetAccountsAsync()).Any();
if (_hasConfiguredAccounts)
{
_preferencesService.PreferenceChanged -= PreferencesServiceChanged;
_preferencesService.PreferenceChanged += PreferencesServiceChanged;
RestartAutoSynchronizationLoop();
}
_activationInfrastructureInitialized = true;
}
finally
@@ -452,6 +460,38 @@ public partial class App : WinoApplication,
}
}
private async Task EnsureAppHostInfrastructureAsync()
{
await EnsureCoreActivationInfrastructureAsync();
if (_appHostInfrastructureInitialized)
return;
await _appHostInfrastructureSemaphore.WaitAsync();
try
{
if (_appHostInfrastructureInitialized)
return;
EnsureWindowManagerConfigured();
EnsureTrayIconCreated();
if (_hasConfiguredAccounts)
{
_preferencesService!.PreferenceChanged -= PreferencesServiceChanged;
_preferencesService.PreferenceChanged += PreferencesServiceChanged;
RestartAutoSynchronizationLoop();
}
_appHostInfrastructureInitialized = true;
}
finally
{
_appHostInfrastructureSemaphore.Release();
}
}
private bool TryMarkInitialNotificationActivationHandled()
=> Interlocked.Exchange(ref _initialNotificationActivationHandled, 1) == 0;
@@ -462,115 +502,123 @@ public partial class App : WinoApplication,
{
base.OnLaunched(args);
await EnsureActivationInfrastructureAsync();
await EnsureCoreActivationInfrastructureAsync();
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
var activationArgs = ResolveStartupActivation();
if (activationArgs.Kind == ExtendedActivationKind.ShareTarget &&
TryMarkInitialShareActivationHandled())
{
LogActivation("Processing share target activation from OnLaunched.");
if (await TryHandleStartupAppNotificationActivationAsync(activationArgs))
return;
if (await HandleShareTargetActivationAsync(activationArgs, activateWindow: true))
return;
}
await EnsureAppHostInfrastructureAsync();
var hasAnyAccount = _hasConfiguredAccounts;
if (!IsStartupTaskLaunch() && !hasAnyAccount)
var isStartupTaskLaunch = activationArgs.Kind == ExtendedActivationKind.StartupTask;
if (!hasAnyAccount && !isStartupTaskLaunch)
{
CreateWelcomeWindow();
await NewThemeService.InitializeAsync();
MainWindow?.Activate();
LogActivation("Welcome window created and activated.");
await LaunchWelcomeWindowAsync();
return;
}
// Check if launched from toast notification.
if (IsNotificationActivation(out AppNotificationActivatedEventArgs toastArgs) &&
TryMarkInitialNotificationActivationHandled())
{
LogActivation($"Processing notification activation from OnLaunched. Arguments: {toastArgs.Argument}");
await HandleToastActivationAsync(toastArgs.Argument, toastArgs.UserInput);
if (await TryHandleLaunchActivationAsync(args, activationArgs))
return;
}
if (ToastActivationResolver.TryParse(args.Arguments, out var launchToastArguments) &&
TryMarkInitialNotificationActivationHandled())
{
LogActivation($"Processing toast launch activation from OnLaunched. Arguments: {args.Arguments}");
await HandleToastActivationAsync(launchToastArguments);
return;
}
// Check if launched by startup task.
bool isStartupTaskLaunch = IsStartupTaskLaunch();
if (isStartupTaskLaunch && !hasAnyAccount)
{
CreateWelcomeWindow();
}
else
{
CreateWindow(args);
}
// Initialize theme service after window creation.
// Theme service requires the window to exist to properly load and apply themes.
await NewThemeService.InitializeAsync();
if (hasAnyAccount)
{
// Wino account loading and activation.
await LoadInitialWinoAccountAsync();
}
LogActivation("Theme service initialized.");
// If startup task launch, keep window hidden (system tray only).
// Otherwise, activate the window normally.
if (isStartupTaskLaunch)
{
LogActivation("Launched by startup task. Window created but hidden (system tray only).");
}
else
{
// Normal launch - show and activate the window.
// The What's New dialog is shown from MailAppShellViewModel.OnNavigatedTo once XamlRoot is ready.
MainWindow?.Activate();
LogActivation("Window created and activated.");
}
await CompleteStandardLaunchAsync(args, hasAnyAccount, isStartupTaskLaunch);
}
public async Task HandleInitialActivationAsync()
private AppActivationArguments ResolveStartupActivation()
{
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
if (Program.TryConsumeDeferredAppNotificationStartup())
{
LogActivation($"Resolved deferred COM activation after notification registration. Kind: {activationArgs.Kind}");
}
return activationArgs;
}
private async Task<bool> TryHandleStartupAppNotificationActivationAsync(AppActivationArguments activationArgs)
{
if (activationArgs.Kind != ExtendedActivationKind.AppNotification ||
activationArgs.Data is not AppNotificationActivatedEventArgs toastArgs ||
!TryMarkInitialNotificationActivationHandled())
{
return;
return false;
}
LogActivation($"Processing initial notification activation from application startup. Arguments: {toastArgs.Argument}");
LogActivation($"Processing app-notification activation from startup. Arguments: {toastArgs.Argument}");
await EnsureActivationInfrastructureAsync();
await HandleToastActivationAsync(toastArgs.Argument, toastArgs.UserInput);
if (!TryResolveNotificationActivationRoute(toastArgs, out var route))
{
await HandleToastActivationAsync(toastArgs.Argument, toastArgs.UserInput);
if (!IsAppRunning())
{
LogActivation("Startup app-notification activation completed without a window. Exiting transient process.");
ExitApplication();
}
return true;
}
if (route.RequiresForegroundWindow)
{
await EnsureAppHostInfrastructureAsync();
await route.ExecuteAsync!.Invoke();
return true;
}
await route.ExecuteAsync!.Invoke();
if (!IsAppRunning())
{
LogActivation("Background startup app-notification activation completed. Exiting without creating app host.");
ExitApplication();
}
return true;
}
private void AppNotificationInvoked(AppNotificationManager sender, AppNotificationActivatedEventArgs args)
{
if (!_activationInfrastructureInitialized)
{
LogActivation($"Buffering app notification activation until infrastructure is ready. Arguments: {args.Argument}");
_bufferedAppNotificationActivations.Enqueue(args);
return;
}
// AppNotification callbacks are not guaranteed to run on the UI thread.
// Marshal toast handling to the window dispatcher before touching window APIs.
if (MainWindow?.DispatcherQueue?.TryEnqueue(() => _ = HandleToastActivationAsync(args.Argument, args.UserInput)) == true)
if (TryEnqueueActivationOnUiThread(() => _ = HandleToastActivationAsync(args.Argument, args.UserInput)))
return;
LogActivation($"Processing notification activation from NotificationInvoked. Arguments: {args.Argument}");
_ = HandleToastActivationAsync(args.Argument, args.UserInput);
}
private void TryRegisterAppNotifications()
private bool TryResolveNotificationActivationRoute(AppNotificationActivatedEventArgs notificationArgs,
out NotificationActivationRoute route)
{
route = default;
if (!ToastActivationResolver.TryParse(notificationArgs.Argument, out var toastArguments))
return false;
return TryCreateNotificationActivationRoute(toastArguments, notificationArgs.UserInput, out route);
}
private void EnsureAppNotificationRegistration()
{
if (!Program.ShouldRegisterAppNotifications())
{
LogActivation("Skipping app notification registration for non-host entry activation.");
return;
}
if (_appNotificationsRegistered)
return;
var notificationManager = AppNotificationManager.Default;
notificationManager.NotificationInvoked -= AppNotificationInvoked;
@@ -579,6 +627,7 @@ public partial class App : WinoApplication,
try
{
notificationManager.Register();
_appNotificationsRegistered = true;
}
catch (Exception ex)
{
@@ -591,70 +640,17 @@ public partial class App : WinoApplication,
/// </summary>
private async Task HandleToastActivationAsync(NotificationArguments toastArguments, IDictionary<string, string>? userInput = null)
{
LogActivation("Handling app notification activation.");
if (toastArguments.TryGetValue(Constants.ToastStoreUpdateActionKey, out string storeUpdateAction) &&
storeUpdateAction == Constants.ToastStoreUpdateActionInstall)
if (!TryCreateNotificationActivationRoute(toastArguments, userInput, out var route))
{
await HandleStoreUpdateToastAsync();
LogActivation("App notification activation did not match any known handler.");
return;
}
if (toastArguments.TryGetValue(Constants.ToastDismissActionKey, out string _))
{
LogActivation("Handling notification dismiss action.");
return;
}
LogActivation(route.RequiresForegroundWindow
? "Handling foreground app notification activation."
: "Handling background app notification activation.");
// Check calendar reminder toast activation first.
if (toastArguments.TryGetValue(Constants.ToastCalendarActionKey, out string calendarAction) &&
toastArguments.TryGetValue(Constants.ToastCalendarItemIdKey, out string calendarItemIdString) &&
Guid.TryParse(calendarItemIdString, out Guid calendarItemId))
{
if (calendarAction == Constants.ToastCalendarNavigateAction)
{
await HandleCalendarToastNavigationAsync(calendarItemId);
return;
}
if (calendarAction == Constants.ToastCalendarSnoozeAction)
{
await HandleCalendarToastSnoozeAsync(userInput, calendarItemId);
return;
}
if (calendarAction == Constants.ToastCalendarJoinOnlineAction)
{
await HandleCalendarToastJoinOnlineAsync(calendarItemId);
return;
}
}
// Check if this is a navigation toast (user clicked the notification).
if (toastArguments.TryGetValue(Constants.ToastActionKey, out MailOperation action) &&
Guid.TryParse(toastArguments[Constants.ToastMailUniqueIdKey], out Guid mailItemUniqueId))
{
if (action == MailOperation.Navigate)
{
// User clicked notification - create window if needed and navigate.
await HandleToastNavigationAsync(mailItemUniqueId);
}
else if (IsComposeToastAction(action))
{
await HandleToastComposeActionAsync(action, mailItemUniqueId);
}
else
{
// User clicked action button (Mark as Read, Delete, etc.)
// Execute action without window and exit.
await HandleToastActionAsync(action, mailItemUniqueId);
}
return;
}
LogActivation("App notification activation did not match any known handler.");
await route.ExecuteAsync!.Invoke();
}
private Task HandleToastActivationAsync(string toastArgument, IDictionary<string, string>? userInput = null)
@@ -705,6 +701,37 @@ public partial class App : WinoApplication,
return true;
}
private async Task<bool> TryHandleLaunchActivationAsync(Microsoft.UI.Xaml.LaunchActivatedEventArgs args,
AppActivationArguments activationArgs)
{
if (activationArgs.Kind == ExtendedActivationKind.ShareTarget &&
TryMarkInitialShareActivationHandled())
{
LogActivation("Processing share target activation from OnLaunched.");
if (await HandleShareTargetActivationAsync(activationArgs, activateWindow: true))
return true;
}
if (Program.TryConsumePendingBootstrapActivation(out var pendingBootstrapActivation))
{
LogActivation($"Processing pending bootstrap activation. Kind: {pendingBootstrapActivation.Kind}, Mode: {pendingBootstrapActivation.Mode}");
if (await HandlePendingBootstrapActivationAsync(pendingBootstrapActivation))
return true;
}
if (ToastActivationResolver.TryParse(args.Arguments, out var launchToastArguments) &&
TryMarkInitialNotificationActivationHandled())
{
LogActivation($"Processing toast launch activation from OnLaunched. Arguments: {args.Arguments}");
await HandleToastActivationAsync(launchToastArguments);
return true;
}
return false;
}
private async Task<MailShareRequest?> ExtractMailShareRequestAsync(ShareTargetActivatedEventArgs shareTargetArgs)
{
var shareOperation = shareTargetArgs.ShareOperation;
@@ -751,22 +778,36 @@ public partial class App : WinoApplication,
}
}
private async Task<IWinoShellWindow?> EnsureShellWindowAsync(WinoApplicationMode mode, bool activateWindow, bool suppressStartupFlows = true)
private async Task LaunchWelcomeWindowAsync()
{
CreateWelcomeWindow();
await NewThemeService.InitializeAsync();
MainWindow?.Activate();
LogActivation("Welcome window created and activated.");
}
private async Task<ShellWindowActivationResult> EnsureShellWindowAsync(WinoApplicationMode mode,
bool activateWindow,
bool suppressStartupFlows = true,
object? activationParameter = null)
{
var windowManager = Services.GetRequiredService<IWinoWindowManager>();
var navigationService = Services.GetRequiredService<INavigationService>();
var shellWindow = windowManager.GetWindow(WinoWindowKind.Shell) as IWinoShellWindow;
var wasCreated = false;
if (shellWindow == null)
{
LogActivation($"Creating shell window for {mode} activation.");
wasCreated = true;
CreateWindow(
null,
AppEntryConstants.GetModeLaunchArgument(mode),
new ShellModeActivationContext
{
SuppressStartupFlows = suppressStartupFlows
SuppressStartupFlows = suppressStartupFlows,
Parameter = activationParameter
});
await NewThemeService.InitializeAsync();
@@ -780,18 +821,22 @@ public partial class App : WinoApplication,
}
else
{
ApplyShellWindowTaskbarIdentity(shellWindow, mode);
navigationService.ChangeApplicationMode(mode, new ShellModeActivationContext
{
SuppressStartupFlows = suppressStartupFlows
SuppressStartupFlows = suppressStartupFlows,
Parameter = activationParameter
});
}
ApplyShellWindowTaskbarIdentity(shellWindow, mode);
if (activateWindow && shellWindow is WindowEx window)
{
await ActivateWindowAsync(window);
await ActivateWindowAsync(window, applyThemeToWindow: wasCreated);
}
return shellWindow;
return new ShellWindowActivationResult(shellWindow, wasCreated);
}
private async Task HandleStoreUpdateToastAsync()
@@ -808,18 +853,42 @@ public partial class App : WinoApplication,
private async Task HandleCalendarToastNavigationAsync(Guid calendarItemId)
{
var calendarService = Services.GetRequiredService<ICalendarService>();
var navigationService = Services.GetRequiredService<INavigationService>();
var fallbackNavigationArgs = new CalendarPageNavigationArgs
{
RequestDefaultNavigation = true
};
if (!HasShellWindow())
{
await EnsureShellWindowAsync(
WinoApplicationMode.Calendar,
activateWindow: true,
activationParameter: fallbackNavigationArgs);
}
var calendarItem = await calendarService.GetCalendarItemAsync(calendarItemId);
if (calendarItem == null)
{
LogActivation($"Calendar notification navigation item was not found for {calendarItemId}. Opening calendar shell only.");
await EnsureShellWindowAsync(
WinoApplicationMode.Calendar,
activateWindow: true,
activationParameter: fallbackNavigationArgs);
return;
}
var target = new CalendarItemTarget(calendarItem, CalendarEventTargetType.Single);
var navigationArgs = new CalendarPageNavigationArgs
{
NavigationDate = calendarItem.LocalStartDate,
PendingTarget = target
};
await EnsureShellWindowAsync(WinoApplicationMode.Calendar, activateWindow: true);
navigationService.ChangeApplicationMode(Core.Domain.Enums.WinoApplicationMode.Calendar);
navigationService.Navigate(WinoPage.EventDetailsPage, target);
await EnsureShellWindowAsync(
WinoApplicationMode.Calendar,
activateWindow: true,
activationParameter: navigationArgs);
}
private async Task HandleCalendarToastSnoozeAsync(IDictionary<string, string>? userInput, Guid calendarItemId)
@@ -848,6 +917,38 @@ public partial class App : WinoApplication,
await nativeAppService.LaunchUriAsync(joinUri);
}
private async Task CompleteStandardLaunchAsync(Microsoft.UI.Xaml.LaunchActivatedEventArgs args,
bool hasAnyAccount,
bool isStartupTaskLaunch)
{
if (isStartupTaskLaunch && !hasAnyAccount)
{
CreateWelcomeWindow();
}
else
{
CreateWindow(args);
}
await NewThemeService.InitializeAsync();
if (hasAnyAccount)
{
await LoadInitialWinoAccountAsync();
}
LogActivation("Theme service initialized.");
if (isStartupTaskLaunch)
{
LogActivation("Launched by startup task. Window created but hidden (system tray only).");
return;
}
MainWindow?.Activate();
LogActivation("Window created and activated.");
}
private bool TryGetSnoozeDurationMinutes(IDictionary<string, string>? userInput, out int snoozeDurationMinutes)
{
snoozeDurationMinutes = _preferencesService?.DefaultSnoozeDurationInMinutes ?? 0;
@@ -871,7 +972,6 @@ public partial class App : WinoApplication,
private async Task HandleToastNavigationAsync(Guid mailItemUniqueId)
{
var mailService = Services.GetRequiredService<IMailService>();
var navigationService = Services.GetRequiredService<INavigationService>();
var account = await mailService.GetMailAccountByUniqueIdAsync(mailItemUniqueId);
if (account == null)
@@ -896,7 +996,6 @@ public partial class App : WinoApplication,
var shellWindowAlreadyExists = HasShellWindow();
await EnsureShellWindowAsync(WinoApplicationMode.Mail, activateWindow: true);
navigationService.ChangeApplicationMode(Core.Domain.Enums.WinoApplicationMode.Mail);
if (shellWindowAlreadyExists)
{
@@ -1036,7 +1135,6 @@ public partial class App : WinoApplication,
}
await EnsureShellWindowAsync(WinoApplicationMode.Mail, activateWindow: true);
navigationService.ChangeApplicationMode(WinoApplicationMode.Mail);
if (mailShellViewModel.MenuItems.TryGetAccountMenuItem(account.Id, out IAccountMenuItem accountMenuItem))
{
@@ -1131,6 +1229,7 @@ public partial class App : WinoApplication,
? resolvedActivationMode
: AppModeActivationResolver.Resolve(args?.Arguments, GetCurrentLaunchTileId(), Environment.CommandLine, defaultMode);
ApplyShellWindowTaskbarIdentity(shellWindow, targetMode);
navigationService.ChangeApplicationMode(targetMode, activationContextOverride);
return;
}
@@ -1164,6 +1263,18 @@ public partial class App : WinoApplication,
shellWindow.HandleAppActivation(args?.Arguments, GetCurrentLaunchTileId(), Environment.CommandLine);
}
private static void ApplyShellWindowTaskbarIdentity(IWinoShellWindow? shellWindow, WinoApplicationMode mode)
{
if (shellWindow is not WindowEx window)
return;
var packagedApplicationId = AppEntryConstants.GetPackagedApplicationId(mode);
if (packagedApplicationId == null)
return;
WindowAppUserModelIdHelper.TrySet(window, AppEntryConstants.GetAppUserModelId(mode));
}
private void CreateWelcomeWindow()
{
LogActivation("Creating welcome window.");
@@ -1642,7 +1753,7 @@ public partial class App : WinoApplication,
// Handle toast notification activation
var toastArgs = (AppNotificationActivatedEventArgs)args.Data;
LogActivation($"Processing redirected notification activation. Arguments: {toastArgs.Argument}");
_ = HandleToastActivationAsync(toastArgs.Argument, toastArgs.UserInput);
await HandleToastActivationAsync(toastArgs.Argument, toastArgs.UserInput);
}
else if (args.Kind == ExtendedActivationKind.ShareTarget)
{
@@ -1662,7 +1773,7 @@ public partial class App : WinoApplication,
{
shouldActivateWindow = ToastActivationResolver.ShouldBringToForeground(launchToastArguments);
LogActivation($"Processing redirected toast launch activation. Arguments: {launchArgs.Arguments}");
_ = HandleToastActivationAsync(launchToastArguments);
await HandleToastActivationAsync(launchToastArguments);
}
else
{
@@ -1692,12 +1803,89 @@ public partial class App : WinoApplication,
}
// Dispatch to UI thread since this is called from Program.OnActivated.
if (MainWindow?.DispatcherQueue.TryEnqueue(() => _ = HandleRedirectedActivationAsync()) == true)
if (TryEnqueueActivationOnUiThread(() => _ = HandleRedirectedActivationAsync()))
return;
_ = HandleRedirectedActivationAsync();
}
private bool TryCreateNotificationActivationRoute(NotificationArguments toastArguments,
IDictionary<string, string>? userInput,
out NotificationActivationRoute route)
{
if (toastArguments.TryGetValue(Constants.ToastStoreUpdateActionKey, out string storeUpdateAction) &&
storeUpdateAction == Constants.ToastStoreUpdateActionInstall)
{
route = new NotificationActivationRoute(true, HandleStoreUpdateToastAsync);
return true;
}
if (toastArguments.TryGetValue(Constants.ToastDismissActionKey, out string _))
{
route = new NotificationActivationRoute(false, () =>
{
LogActivation("Handling notification dismiss action.");
return Task.CompletedTask;
});
return true;
}
if (toastArguments.TryGetValue(Constants.ToastCalendarActionKey, out string calendarAction) &&
toastArguments.TryGetValue(Constants.ToastCalendarItemIdKey, out string calendarItemIdString) &&
Guid.TryParse(calendarItemIdString, out Guid calendarItemId))
{
route = calendarAction switch
{
Constants.ToastCalendarNavigateAction => new NotificationActivationRoute(true, () => HandleCalendarToastNavigationAsync(calendarItemId)),
Constants.ToastCalendarSnoozeAction => new NotificationActivationRoute(false, () => HandleCalendarToastSnoozeAsync(userInput, calendarItemId)),
Constants.ToastCalendarJoinOnlineAction => new NotificationActivationRoute(false, () => HandleCalendarToastJoinOnlineAsync(calendarItemId)),
_ => default
};
return route.ExecuteAsync != null;
}
if (toastArguments.TryGetValue(Constants.ToastActionKey, out MailOperation action) &&
Guid.TryParse(toastArguments[Constants.ToastMailUniqueIdKey], out Guid mailItemUniqueId))
{
if (action == MailOperation.Navigate)
{
route = new NotificationActivationRoute(true, () => HandleToastNavigationAsync(mailItemUniqueId));
return true;
}
if (IsComposeToastAction(action))
{
route = new NotificationActivationRoute(true, () => HandleToastComposeActionAsync(action, mailItemUniqueId));
return true;
}
route = new NotificationActivationRoute(false, () => HandleToastActionAsync(action, mailItemUniqueId));
return true;
}
route = default;
return false;
}
private async Task<bool> HandlePendingBootstrapActivationAsync(PendingBootstrapActivation pendingBootstrapActivation)
{
if (pendingBootstrapActivation.Mode != WinoApplicationMode.Calendar)
return false;
var navigationArgs = new CalendarPageNavigationArgs
{
RequestDefaultNavigation = true
};
await EnsureShellWindowAsync(
WinoApplicationMode.Calendar,
activateWindow: true,
activationParameter: navigationArgs);
return true;
}
private static string AppendLaunchArgument(string? launchArguments, string launchArgument)
{
return string.IsNullOrWhiteSpace(launchArguments)
@@ -1778,6 +1966,32 @@ public partial class App : WinoApplication,
return null;
}
private bool TryEnqueueActivationOnUiThread(Action action)
{
var dispatcherQueue = MainWindow?.DispatcherQueue;
if (dispatcherQueue == null)
{
var windowManager = Services.GetService<IWinoWindowManager>();
var currentWindow = windowManager?.ActiveWindow
?? windowManager?.GetWindow(WinoWindowKind.Shell)
?? windowManager?.GetWindow(WinoWindowKind.Welcome);
dispatcherQueue = currentWindow?.DispatcherQueue;
}
if (dispatcherQueue == null)
return false;
if (dispatcherQueue.HasThreadAccess)
{
action();
return true;
}
return dispatcherQueue.TryEnqueue(() => action());
}
}