Toast actions.
This commit is contained in:
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
@@ -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()
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user