Toast actions.

This commit is contained in:
Burak Kaan Köse
2025-11-14 11:37:26 +01:00
parent 8482171bf2
commit 6be271565e
8 changed files with 282 additions and 135 deletions
+20 -6
View File
@@ -24,12 +24,14 @@ public class OutlookAuthenticator : BaseAuthenticator, IOutlookAuthenticator
public override MailProviderType ProviderType => MailProviderType.Outlook; public override MailProviderType ProviderType => MailProviderType.Outlook;
private readonly IPublicClientApplication _publicClientApplication; private readonly IPublicClientApplication _publicClientApplication;
private readonly INativeAppService _nativeAppService;
private readonly IApplicationConfiguration _applicationConfiguration; private readonly IApplicationConfiguration _applicationConfiguration;
public OutlookAuthenticator(INativeAppService nativeAppService, public OutlookAuthenticator(INativeAppService nativeAppService,
IApplicationConfiguration applicationConfiguration, IApplicationConfiguration applicationConfiguration,
IAuthenticatorConfig authenticatorConfig) : base(authenticatorConfig) IAuthenticatorConfig authenticatorConfig) : base(authenticatorConfig)
{ {
_nativeAppService = nativeAppService;
_applicationConfiguration = applicationConfiguration; _applicationConfiguration = applicationConfiguration;
var authenticationRedirectUri = nativeAppService.GetWebAuthenticationBrokerUri(); var authenticationRedirectUri = nativeAppService.GetWebAuthenticationBrokerUri();
@@ -40,12 +42,24 @@ public class OutlookAuthenticator : BaseAuthenticator, IOutlookAuthenticator
ListOperatingSystemAccounts = true, ListOperatingSystemAccounts = true,
}; };
PublicClientApplicationBuilder outlookAppBuilder = null;
var outlookAppBuilder = PublicClientApplicationBuilder.Create(AuthenticatorConfig.OutlookAuthenticatorClientId) // Being created from an app notification.
.WithParentActivityOrWindow(nativeAppService.GetCoreWindowHwnd) // This is where we avoid all interactive shit for authentication.
.WithBroker(options) if (nativeAppService.GetCoreWindowHwnd == null)
.WithDefaultRedirectUri() {
.WithAuthority(Authority); outlookAppBuilder = PublicClientApplicationBuilder.Create(AuthenticatorConfig.OutlookAuthenticatorClientId)
.WithDefaultRedirectUri()
.WithBroker(options)
.WithAuthority(Authority);
}
else
{
outlookAppBuilder = PublicClientApplicationBuilder.Create(AuthenticatorConfig.OutlookAuthenticatorClientId)
.WithBroker(options)
.WithDefaultRedirectUri()
.WithAuthority(Authority);
}
_publicClientApplication = outlookAppBuilder.Build(); _publicClientApplication = outlookAppBuilder.Build();
} }
@@ -99,7 +113,7 @@ public class OutlookAuthenticator : BaseAuthenticator, IOutlookAuthenticator
{ {
await EnsureTokenCacheAttachedAsync(); await EnsureTokenCacheAttachedAsync();
var authResult = await _publicClientApplication AuthenticationResult authResult = await _publicClientApplication
.AcquireTokenInteractive(Scope) .AcquireTokenInteractive(Scope)
.ExecuteAsync(); .ExecuteAsync();
+14 -14
View File
@@ -52,23 +52,23 @@ public class NotificationBuilder : INotificationBuilder
{ {
var mailItem = await _mailService.GetSingleMailItemAsync(item.UniqueId); var mailItem = await _mailService.GetSingleMailItemAsync(item.UniqueId);
//if (mailItem == null || mailItem.AssignedFolder == null) if (mailItem == null || mailItem.AssignedFolder == null)
// continue; continue;
//// Only create notifications for Inbox folder mails // Only create notifications for Inbox folder mails
//if (mailItem.AssignedFolder.SpecialFolderType != SpecialFolderType.Inbox) if (mailItem.AssignedFolder.SpecialFolderType != SpecialFolderType.Inbox)
// continue; continue;
//// Skip folders with synchronization disabled // Skip folders with synchronization disabled
//if (!mailItem.AssignedFolder.IsSynchronizationEnabled) if (!mailItem.AssignedFolder.IsSynchronizationEnabled)
// continue; continue;
//// Skip already read mails // Skip already read mails
//if (mailItem.IsRead) if (mailItem.IsRead)
//{ {
// RemoveNotification(mailItem.UniqueId); RemoveNotification(mailItem.UniqueId);
// continue; continue;
//} }
inboxMailItems.Add(mailItem); inboxMailItems.Add(mailItem);
} }
-3
View File
@@ -11,7 +11,6 @@ using Microsoft.Windows.Globalization;
using Nito.AsyncEx; using Nito.AsyncEx;
using Serilog; using Serilog;
using Windows.ApplicationModel.Activation; using Windows.ApplicationModel.Activation;
using Windows.ApplicationModel.AppService;
using Windows.ApplicationModel.Core; using Windows.ApplicationModel.Core;
using Windows.Foundation.Metadata; using Windows.Foundation.Metadata;
using Windows.Storage; using Windows.Storage;
@@ -84,9 +83,7 @@ public abstract class WinoApplication : Application, IRecipient<LanguageChanged>
{ {
yield return DatabaseService; yield return DatabaseService;
yield return TranslationService; yield return TranslationService;
yield return NewThemeService; // Initialize NewThemeService instead of old ThemeService
yield return Services.GetService<SynchronizationManagerInitializer>(); yield return Services.GetService<SynchronizationManagerInitializer>();
// yield return ThemeService; // Keep old service for backward compatibility but don't initialize
} }
public Task InitializeServicesAsync() => GetActivationServices().Select(a => a.InitializeAsync()).WhenAll(); public Task InitializeServicesAsync() => GetActivationServices().Select(a => a.InitializeAsync()).WhenAll();
+4 -20
View File
@@ -42,6 +42,7 @@ public class SynchronizationManager : ISynchronizationManager
/// <summary> /// <summary>
/// Initializes the SynchronizationManager with required dependencies. /// Initializes the SynchronizationManager with required dependencies.
/// This must be called before using any other methods. /// This must be called before using any other methods.
/// Note: Synchronizers are created lazily to avoid requiring window handles during app initialization.
/// </summary> /// </summary>
/// <param name="synchronizerFactory">Factory for creating synchronizers</param> /// <param name="synchronizerFactory">Factory for creating synchronizers</param>
/// <param name="imapTestService">Service for testing IMAP connectivity</param> /// <param name="imapTestService">Service for testing IMAP connectivity</param>
@@ -65,28 +66,11 @@ public class SynchronizationManager : ISynchronizationManager
_authenticationProvider = authenticationProvider ?? throw new ArgumentNullException(nameof(authenticationProvider)); _authenticationProvider = authenticationProvider ?? throw new ArgumentNullException(nameof(authenticationProvider));
_notificationBuilder = notificationBuilder ?? throw new ArgumentNullException(nameof(notificationBuilder)); _notificationBuilder = notificationBuilder ?? throw new ArgumentNullException(nameof(notificationBuilder));
// Get all accounts and create synchronizers for them // DO NOT create synchronizers here to avoid requiring window handles during initialization.
var accounts = await _accountService.GetAccountsAsync(); // Synchronizers will be created lazily when first accessed via GetOrCreateSynchronizerAsync.
foreach (var account in accounts)
{
try
{
var synchronizer = _concreteSynchronizerFactory.CreateNewSynchronizer(account);
_synchronizerCache.TryAdd(account.Id, synchronizer);
_logger.Information("Created synchronizer for account {AccountName} ({AccountId})",
account.Name, account.Id);
}
catch (Exception ex)
{
_logger.Error(ex, "Failed to create synchronizer for account {AccountName} ({AccountId})",
account.Name, account.Id);
}
}
_isInitialized = true; _isInitialized = true;
_logger.Information("SynchronizationManager initialized with {Count} synchronizers", _synchronizerCache.Count); _logger.Information("SynchronizationManager dependencies initialized. Synchronizers will be created lazily.");
} }
finally finally
{ {
+7 -8
View File
@@ -20,16 +20,14 @@ public class SynchronizerFactory : ISynchronizerFactory
private readonly IOutlookChangeProcessor _outlookChangeProcessor; private readonly IOutlookChangeProcessor _outlookChangeProcessor;
private readonly IGmailChangeProcessor _gmailChangeProcessor; private readonly IGmailChangeProcessor _gmailChangeProcessor;
private readonly IImapChangeProcessor _imapChangeProcessor; private readonly IImapChangeProcessor _imapChangeProcessor;
private readonly IOutlookAuthenticator _outlookAuthenticator; private readonly IAuthenticationProvider _authenticationProvider;
private readonly IGmailAuthenticator _gmailAuthenticator;
private readonly List<IWinoSynchronizerBase> synchronizerCache = new(); private readonly List<IWinoSynchronizerBase> synchronizerCache = new();
public SynchronizerFactory(IOutlookChangeProcessor outlookChangeProcessor, public SynchronizerFactory(IOutlookChangeProcessor outlookChangeProcessor,
IGmailChangeProcessor gmailChangeProcessor, IGmailChangeProcessor gmailChangeProcessor,
IImapChangeProcessor imapChangeProcessor, IImapChangeProcessor imapChangeProcessor,
IOutlookAuthenticator outlookAuthenticator, IAuthenticationProvider authenticationProvider,
IGmailAuthenticator gmailAuthenticator,
IAccountService accountService, IAccountService accountService,
IImapSynchronizationStrategyProvider imapSynchronizationStrategyProvider, IImapSynchronizationStrategyProvider imapSynchronizationStrategyProvider,
IApplicationConfiguration applicationConfiguration, IApplicationConfiguration applicationConfiguration,
@@ -39,8 +37,7 @@ public class SynchronizerFactory : ISynchronizerFactory
_outlookChangeProcessor = outlookChangeProcessor; _outlookChangeProcessor = outlookChangeProcessor;
_gmailChangeProcessor = gmailChangeProcessor; _gmailChangeProcessor = gmailChangeProcessor;
_imapChangeProcessor = imapChangeProcessor; _imapChangeProcessor = imapChangeProcessor;
_outlookAuthenticator = outlookAuthenticator; _authenticationProvider = authenticationProvider;
_gmailAuthenticator = gmailAuthenticator;
_accountService = accountService; _accountService = accountService;
_imapSynchronizationStrategyProvider = imapSynchronizationStrategyProvider; _imapSynchronizationStrategyProvider = imapSynchronizationStrategyProvider;
_applicationConfiguration = applicationConfiguration; _applicationConfiguration = applicationConfiguration;
@@ -75,9 +72,11 @@ public class SynchronizerFactory : ISynchronizerFactory
switch (providerType) switch (providerType)
{ {
case Domain.Enums.MailProviderType.Outlook: case Domain.Enums.MailProviderType.Outlook:
return new OutlookSynchronizer(mailAccount, _outlookAuthenticator, _outlookChangeProcessor, _outlookSynchronizerErrorHandlerFactory); var outlookAuthenticator = _authenticationProvider.GetAuthenticator(Domain.Enums.MailProviderType.Outlook) as IOutlookAuthenticator;
return new OutlookSynchronizer(mailAccount, outlookAuthenticator, _outlookChangeProcessor, _outlookSynchronizerErrorHandlerFactory);
case Domain.Enums.MailProviderType.Gmail: case Domain.Enums.MailProviderType.Gmail:
return new GmailSynchronizer(mailAccount, _gmailAuthenticator, _gmailChangeProcessor, _gmailSynchronizerErrorHandlerFactory); var gmailAuthenticator = _authenticationProvider.GetAuthenticator(Domain.Enums.MailProviderType.Gmail) as IGmailAuthenticator;
return new GmailSynchronizer(mailAccount, gmailAuthenticator, _gmailChangeProcessor, _gmailSynchronizerErrorHandlerFactory);
case Domain.Enums.MailProviderType.IMAP4: case Domain.Enums.MailProviderType.IMAP4:
return new ImapSynchronizer(mailAccount, _imapChangeProcessor, _imapSynchronizationStrategyProvider, _applicationConfiguration); return new ImapSynchronizer(mailAccount, _imapChangeProcessor, _imapSynchronizationStrategyProvider, _applicationConfiguration);
default: default:
@@ -48,7 +48,7 @@ internal class ToastNotificationActivationHandler : ActivationHandler<ToastNotif
var message = new AccountMenuItemExtended(mailItem.AssignedFolder.Id, mailItem); var message = new AccountMenuItemExtended(mailItem.AssignedFolder.Id, mailItem);
// Delegate this event to LaunchProtocolService so app shell can pick it up on launch if app doesn't work. // Delegate this event to LaunchProtocolService so app shell can pick it up on launch if app doesn't work.
var launchProtocolService = App.Current.Services.GetService<ILaunchProtocolService>(); var launchProtocolService = App.Current.Services.GetRequiredService<ILaunchProtocolService>();
launchProtocolService.LaunchParameter = message; launchProtocolService.LaunchParameter = message;
// Send the messsage anyways. Launch protocol service will be ignored if the message is picked up by subscriber shell. // Send the messsage anyways. Launch protocol service will be ignored if the message is picked up by subscriber shell.
+233 -80
View File
@@ -1,13 +1,18 @@
using System; using System;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Toolkit.Uwp.Notifications; using Microsoft.Toolkit.Uwp.Notifications;
using Microsoft.UI.Xaml;
using Microsoft.Windows.AppLifecycle; using Microsoft.Windows.AppLifecycle;
using Microsoft.Windows.AppNotifications;
using Wino.Core.Domain; using Wino.Core.Domain;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Core.WinUI; using Wino.Core.WinUI;
using Wino.Core.WinUI.Interfaces; using Wino.Core.WinUI.Interfaces;
using Wino.Mail.Services; using Wino.Mail.Services;
@@ -26,75 +31,23 @@ public partial class App : WinoApplication, IRecipient<NewMailSynchronizationReq
InitializeComponent(); InitializeComponent();
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
ToastNotificationManagerCompat.OnActivated += ToastActivationHandler;
RegisterRecipients(); RegisterRecipients();
} }
private async void ToastActivationHandler(ToastNotificationActivatedEventArgsCompat e) public bool IsNotificationActivation(out AppNotificationActivatedEventArgs args)
{ {
// If we weren't launched by an app, launch our window like normal. // https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/migrate-to-windows-app-sdk/guides/toast-notifications?tabs=appsdk
// Otherwise if launched by a toast, our OnActivated callback will be triggered. var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
var toastArgs = ToastArguments.Parse(e.Argument); if (activationArgs.Kind == ExtendedActivationKind.AppNotification)
var mailService = Services.GetRequiredService<IMailService>();
var accountService = Services.GetRequiredService<IAccountService>();
var actionKey = toastArgs.Contains(Constants.ToastActionKey) ? toastArgs[Constants.ToastActionKey] : string.Empty;
if (Guid.TryParse(toastArgs[Constants.ToastMailUniqueIdKey], out Guid mailItemUniqueId))
{ {
// Action triggered. args = ((AppNotificationActivatedEventArgs)activationArgs.Data);
if (toastArgs.TryGetValue(Constants.ToastActionKey, out MailOperation action)) return true;
{
// Check if the app is running.
if (IsAppRunning())
{
// Get the synchronizer and queue the action for the given item.
var processor = Services.GetRequiredService<IWinoRequestProcessor>();
var delegator = Services.GetRequiredService<IWinoRequestDelegator>();
var mailItem = await mailService.GetSingleMailItemAsync(mailItemUniqueId);
if (mailItem != null)
{
var package = new MailOperationPreperationRequest(action, mailItem);
await delegator.ExecuteAsync(package);
}
}
else
{
}
}
else
{
// Notification clicked. Handle navigation.
var account = await mailService.GetMailAccountByUniqueIdAsync(mailItemUniqueId).ConfigureAwait(false);
if (account == null) return;
var mailItem = await mailService.GetSingleMailItemAsync(mailItemUniqueId).ConfigureAwait(false);
if (mailItem == null) return;
var message = new AccountMenuItemExtended(mailItem.AssignedFolder.Id, mailItem);
// Delegate this event to LaunchProtocolService so app shell can pick it up on launch if app doesn't work.
var launchProtocolService = Services.GetRequiredService<ILaunchProtocolService>();
launchProtocolService.LaunchParameter = message;
// Send the messsage anyways. Launch protocol service will be ignored if the message is picked up by subscriber shell.
WeakReferenceMessenger.Default.Send(message);
}
} }
if (ToastNotificationManagerCompat.WasCurrentProcessToastActivated()) args = null!;
{ return false;
MainWindow.BringToFront();
}
} }
#region Dependency Injection #region Dependency Injection
@@ -155,38 +108,238 @@ public partial class App : WinoApplication, IRecipient<NewMailSynchronizationReq
protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{ {
// If it's toast activation, compat will handle it. base.OnLaunched(args);
if (IsAppRunning()) return;
// TODO: Check app relaunch mutex before loading anything. AppNotificationManager notificationManager = AppNotificationManager.Default;
// Initialize NewThemeService first to get backdrop settings before creating window notificationManager.NotificationInvoked -= AppNotificationInvoked;
var newThemeService = Services.GetRequiredService<INewThemeService>(); notificationManager.NotificationInvoked += AppNotificationInvoked;
var configService = Services.GetRequiredService<IConfigurationService>(); notificationManager.Register();
var nativeAppService = Services.GetRequiredService<INativeAppService>();
// Initialize required services regardless of launch activation type.
// All activation scenarios require these services to be ready.
// Note: Theme service is initialized separately after window creation.
await InitializeServicesAsync();
_synchronizationManager = Services.GetRequiredService<ISynchronizationManager>(); _synchronizationManager = Services.GetRequiredService<ISynchronizationManager>();
// Load saved backdrop type before creating window // Check if launched from toast notification.
var savedBackdropType = (WindowBackdropType)configService.Get("WindowBackdropTypeKey", (int)WindowBackdropType.Mica); if (IsNotificationActivation(out AppNotificationActivatedEventArgs toastArgs))
{
await HandleToastActivationAsync(toastArgs);
return;
}
// Check if launched by startup task.
bool isStartupTaskLaunch = IsStartupTaskLaunch();
// Create the window (needed for system tray icon even in startup task scenario).
CreateWindow(args);
// Initialize theme service after window creation.
// Theme service requires the window to exist to properly load and apply themes.
await NewThemeService.InitializeAsync();
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).");
// Window is created but not activated. User can show it from system tray.
}
else
{
// Normal launch - show and activate the window.
MainWindow.Activate();
LogActivation("Window created and activated.");
}
}
private async void AppNotificationInvoked(AppNotificationManager sender, AppNotificationActivatedEventArgs args)
=> await HandleToastActivationAsync(args);
/// <summary>
/// Handles toast notification activation scenarios.
/// </summary>
private async Task HandleToastActivationAsync(AppNotificationActivatedEventArgs toastArgs)
{
var toastArguments = ToastArguments.Parse(toastArgs.Argument);
// 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
{
// User clicked action button (Mark as Read, Delete, etc.)
// Execute action without window and exit.
await HandleToastActionAsync(action, mailItemUniqueId);
}
}
}
/// <summary>
/// Handles toast notification click for navigation.
/// Creates window if not running, sets up navigation parameter.
/// </summary>
private async Task HandleToastNavigationAsync(Guid mailItemUniqueId)
{
var mailService = Services.GetRequiredService<IMailService>();
var account = await mailService.GetMailAccountByUniqueIdAsync(mailItemUniqueId).ConfigureAwait(false);
if (account == null) return;
var mailItem = await mailService.GetSingleMailItemAsync(mailItemUniqueId).ConfigureAwait(false);
if (mailItem == null) return;
var message = new AccountMenuItemExtended(mailItem.AssignedFolder.Id, mailItem);
// Store navigation parameter in LaunchProtocolService so AppShell can pick it up.
var launchProtocolService = Services.GetRequiredService<ILaunchProtocolService>();
launchProtocolService.LaunchParameter = message;
// Create window if not already created.
if (!IsAppRunning())
{
// Pass null for args since we're handling toast navigation
await CreateAndActivateWindow(null!);
}
else
{
// App is already running - send message and bring window to front.
WeakReferenceMessenger.Default.Send(message);
MainWindow.BringToFront();
}
}
/// <summary>
/// Handles toast action button clicks (Mark as Read, Delete, etc.).
/// Executes the action without showing UI and exits the app.
/// </summary>
private async Task HandleToastActionAsync(MailOperation action, Guid mailItemUniqueId)
{
LogActivation($"Handling toast action: {action} for mail {mailItemUniqueId}");
var mailService = Services.GetRequiredService<IMailService>();
var mailItem = await mailService.GetSingleMailItemAsync(mailItemUniqueId);
if (mailItem == null)
{
LogActivation("Mail item not found. Exiting.");
Application.Current.Exit();
return;
}
var package = new MailOperationPreperationRequest(action, mailItem);
// Check if app is already running (has a window).
if (IsAppRunning())
{
// App is running - use the simple delegator pattern.
// The synchronization will happen in the background.
LogActivation("App is running. Queueing request via delegator.");
var delegator = Services.GetRequiredService<IWinoRequestDelegator>();
await delegator.ExecuteAsync(package);
// Don't exit - app continues running.
LogActivation($"Toast action {action} queued successfully.");
}
else
{
// App is not running - we need to wait for sync before exiting.
LogActivation("App is not running. Executing synchronization and waiting for completion.");
if (_synchronizationManager == null)
{
LogActivation("Synchronization manager is not initialized. Exiting.");
Application.Current.Exit();
return;
}
var processor = Services.GetRequiredService<IWinoRequestProcessor>();
var notificationBuilder = Services.GetRequiredService<INotificationBuilder>();
// Prepare the requests for the action.
var requests = await processor.PrepareRequestsAsync(package);
if (requests != null && requests.Any())
{
// Group requests by account ID (usually just one account).
var accountIds = requests.GroupBy(a => a.Item.AssignedAccount.Id);
foreach (var accountGroup in accountIds)
{
var accountId = accountGroup.Key;
// Queue all requests for this account.
foreach (var request in accountGroup)
{
await _synchronizationManager.QueueRequestAsync(request, accountId, triggerSynchronization: false);
}
// Create synchronization options to execute the queued requests.
var syncOptions = new MailSynchronizationOptions()
{
AccountId = accountId,
Type = MailSynchronizationType.ExecuteRequests
};
LogActivation($"Executing synchronization for account {accountId}...");
// Wait for synchronization to complete before exiting.
var syncResult = await _synchronizationManager.SynchronizeMailAsync(syncOptions);
LogActivation($"Toast action {action} completed. Sync result: {syncResult.CompletedState}");
}
await notificationBuilder.UpdateTaskbarIconBadgeAsync();
}
LogActivation("Toast action handling complete. Exiting app.");
// Exit the app after synchronization is complete.
Application.Current.Exit();
}
}
/// <summary>
/// Creates the main window and activates it.
/// </summary>
private async Task CreateAndActivateWindow(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
CreateWindow(args);
// Initialize theme service after window is created.
await NewThemeService.InitializeAsync();
MainWindow.Activate();
LogActivation("Window created and activated.");
}
/// <summary>
/// Creates the main window without activating it.
/// Used for both normal launch and startup task launch (tray only).
/// </summary>
private void CreateWindow(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
LogActivation("Creating main window.");
MainWindow = new ShellWindow(); MainWindow = new ShellWindow();
var nativeAppService = Services.GetRequiredService<INativeAppService>();
nativeAppService.GetCoreWindowHwnd = () => WinRT.Interop.WindowNative.GetWindowHandle(MainWindow); nativeAppService.GetCoreWindowHwnd = () => WinRT.Interop.WindowNative.GetWindowHandle(MainWindow);
await InitializeServicesAsync(); if (MainWindow is not IWinoShellWindow shellWindow)
throw new ArgumentException("MainWindow must implement IWinoShellWindow");
if (MainWindow is not IWinoShellWindow shellWindow) throw new ArgumentException("MainWindow must implement IWinoShellWindow");
bool isStartupTaskLaunch = IsStartupTaskLaunch();
shellWindow.HandleAppActivation(args); shellWindow.HandleAppActivation(args);
// Do not actiavate window if launched from startup task. Keep running in the system tray.
if (!isStartupTaskLaunch)
{
MainWindow.Activate();
}
} }
private void RegisterRecipients() private void RegisterRecipients()
+3 -3
View File
@@ -8,7 +8,7 @@
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10" xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap com"> IgnorableNamespaces="uap rescap com desktop">
<!-- Publisher Cache Folders --> <!-- Publisher Cache Folders -->
<Extensions> <Extensions>
@@ -46,7 +46,7 @@
Executable="$targetnametoken$.exe" Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$"> EntryPoint="$targetentrypoint$">
<uap:VisualElements <uap:VisualElements
DisplayName="Wino Mail (Preview)" DisplayName="Wino Mail"
Description="Wino.Mail.WinUI" Description="Wino.Mail.WinUI"
BackgroundColor="transparent" BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png" Square150x150Logo="Assets\Square150x150Logo.png"
@@ -70,7 +70,7 @@
<com:Extension Category="windows.comServer"> <com:Extension Category="windows.comServer">
<com:ComServer> <com:ComServer>
<com:ExeServer Executable="Wino.Mail.WinUI.exe" Arguments="-ToastActivated" DisplayName="Toast activator"> <com:ExeServer Executable="Wino.Mail.WinUI.exe" Arguments="----AppNotificationActivated:" DisplayName="Toast activator">
<com:Class Id="72c6d2d0-2538-44fe-a1b1-499f47bb1181" DisplayName="Toast activator"/> <com:Class Id="72c6d2d0-2538-44fe-a1b1-499f47bb1181" DisplayName="Toast activator"/>
</com:ExeServer> </com:ExeServer>
</com:ComServer> </com:ComServer>