Wino Calendar app entry. (#819)
* Double app entry. * New icon for sys tray
@@ -16,6 +16,9 @@ public static class Constants
|
|||||||
public const string ToastCalendarItemIdKey = nameof(ToastCalendarItemIdKey);
|
public const string ToastCalendarItemIdKey = nameof(ToastCalendarItemIdKey);
|
||||||
public const string ToastCalendarActionKey = nameof(ToastCalendarActionKey);
|
public const string ToastCalendarActionKey = nameof(ToastCalendarActionKey);
|
||||||
public const string ToastCalendarNavigateAction = nameof(ToastCalendarNavigateAction);
|
public const string ToastCalendarNavigateAction = nameof(ToastCalendarNavigateAction);
|
||||||
|
public const string ToastModeKey = nameof(ToastModeKey);
|
||||||
|
public const string ToastModeMail = nameof(ToastModeMail);
|
||||||
|
public const string ToastModeCalendar = nameof(ToastModeCalendar);
|
||||||
|
|
||||||
public const string ClientLogFile = "Client_.log";
|
public const string ClientLogFile = "Client_.log";
|
||||||
public const string ServerLogFile = "Server_.log";
|
public const string ServerLogFile = "Server_.log";
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Wino.Mail.WinUI.Activation;
|
||||||
|
|
||||||
|
internal static class AppModeActivationResolver
|
||||||
|
{
|
||||||
|
public static WinoApplicationMode Resolve(string? launchArguments, string? tileId, string? appId)
|
||||||
|
{
|
||||||
|
if (TryResolveFromText(launchArguments, out var mode))
|
||||||
|
return mode;
|
||||||
|
|
||||||
|
if (TryResolveFromText(tileId, out mode))
|
||||||
|
return mode;
|
||||||
|
|
||||||
|
if (TryResolveFromText(appId, out mode))
|
||||||
|
return mode;
|
||||||
|
|
||||||
|
return WinoApplicationMode.Mail;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryResolveFromText(string? value, out WinoApplicationMode mode)
|
||||||
|
{
|
||||||
|
mode = WinoApplicationMode.Mail;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
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-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);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -11,6 +12,7 @@ using Microsoft.UI.Xaml;
|
|||||||
using Microsoft.Windows.AppLifecycle;
|
using Microsoft.Windows.AppLifecycle;
|
||||||
using Microsoft.Windows.AppNotifications;
|
using Microsoft.Windows.AppNotifications;
|
||||||
using MimeKit.Cryptography;
|
using MimeKit.Cryptography;
|
||||||
|
using Windows.ApplicationModel.Activation;
|
||||||
using Wino.Calendar.ViewModels;
|
using Wino.Calendar.ViewModels;
|
||||||
using Wino.Calendar.ViewModels.Interfaces;
|
using Wino.Calendar.ViewModels.Interfaces;
|
||||||
using Wino.Core;
|
using Wino.Core;
|
||||||
@@ -22,11 +24,12 @@ using Wino.Core.Domain.Models.MailItem;
|
|||||||
using Wino.Core.Domain.Models.Synchronization;
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
using Wino.Mail.Services;
|
using Wino.Mail.Services;
|
||||||
using Wino.Mail.ViewModels;
|
using Wino.Mail.ViewModels;
|
||||||
|
using Wino.Mail.WinUI.Activation;
|
||||||
using Wino.Mail.WinUI.Interfaces;
|
using Wino.Mail.WinUI.Interfaces;
|
||||||
using Wino.Mail.WinUI.Services;
|
using Wino.Mail.WinUI.Services;
|
||||||
using Wino.Messaging.UI;
|
|
||||||
using Wino.Messaging.Client.Accounts;
|
using Wino.Messaging.Client.Accounts;
|
||||||
using Wino.Messaging.Server;
|
using Wino.Messaging.Server;
|
||||||
|
using Wino.Messaging.UI;
|
||||||
using Wino.Services;
|
using Wino.Services;
|
||||||
namespace Wino.Mail.WinUI;
|
namespace Wino.Mail.WinUI;
|
||||||
|
|
||||||
@@ -133,11 +136,10 @@ public partial class App : WinoApplication,
|
|||||||
{
|
{
|
||||||
base.OnLaunched(args);
|
base.OnLaunched(args);
|
||||||
|
|
||||||
AppNotificationManager notificationManager = AppNotificationManager.Default;
|
if (ShouldRegisterAppNotifications(args))
|
||||||
|
{
|
||||||
notificationManager.NotificationInvoked -= AppNotificationInvoked;
|
TryRegisterAppNotifications();
|
||||||
notificationManager.NotificationInvoked += AppNotificationInvoked;
|
}
|
||||||
notificationManager.Register();
|
|
||||||
|
|
||||||
// Initialize required services regardless of launch activation type.
|
// Initialize required services regardless of launch activation type.
|
||||||
// All activation scenarios require these services to be ready.
|
// All activation scenarios require these services to be ready.
|
||||||
@@ -188,6 +190,42 @@ public partial class App : WinoApplication,
|
|||||||
private async void AppNotificationInvoked(AppNotificationManager sender, AppNotificationActivatedEventArgs args)
|
private async void AppNotificationInvoked(AppNotificationManager sender, AppNotificationActivatedEventArgs args)
|
||||||
=> await HandleToastActivationAsync(args);
|
=> await HandleToastActivationAsync(args);
|
||||||
|
|
||||||
|
private bool ShouldRegisterAppNotifications(Microsoft.UI.Xaml.LaunchActivatedEventArgs? args)
|
||||||
|
{
|
||||||
|
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
|
||||||
|
|
||||||
|
// Always allow registration when activated from a toast.
|
||||||
|
if (activationArgs.Kind == ExtendedActivationKind.AppNotification)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var launchMode = AppModeActivationResolver.Resolve(args?.Arguments, GetCurrentLaunchTileId(), Environment.CommandLine);
|
||||||
|
bool shouldRegister = launchMode == WinoApplicationMode.Mail;
|
||||||
|
|
||||||
|
if (!shouldRegister)
|
||||||
|
{
|
||||||
|
LogActivation("Skipping app notification registration for non-mail launch mode.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return shouldRegister;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryRegisterAppNotifications()
|
||||||
|
{
|
||||||
|
var notificationManager = AppNotificationManager.Default;
|
||||||
|
|
||||||
|
notificationManager.NotificationInvoked -= AppNotificationInvoked;
|
||||||
|
notificationManager.NotificationInvoked += AppNotificationInvoked;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
notificationManager.Register();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogActivation($"App notification registration failed: {ex.GetType().Name} - {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles toast notification activation scenarios.
|
/// Handles toast notification activation scenarios.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -256,6 +294,7 @@ public partial class App : WinoApplication,
|
|||||||
private async Task HandleToastNavigationAsync(Guid mailItemUniqueId)
|
private async Task HandleToastNavigationAsync(Guid mailItemUniqueId)
|
||||||
{
|
{
|
||||||
var mailService = Services.GetRequiredService<IMailService>();
|
var mailService = Services.GetRequiredService<IMailService>();
|
||||||
|
var navigationService = Services.GetRequiredService<INavigationService>();
|
||||||
|
|
||||||
var account = await mailService.GetMailAccountByUniqueIdAsync(mailItemUniqueId).ConfigureAwait(false);
|
var account = await mailService.GetMailAccountByUniqueIdAsync(mailItemUniqueId).ConfigureAwait(false);
|
||||||
if (account == null) return;
|
if (account == null) return;
|
||||||
@@ -278,6 +317,7 @@ public partial class App : WinoApplication,
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// App is already running - send message and bring window to front.
|
// App is already running - send message and bring window to front.
|
||||||
|
navigationService.ChangeApplicationMode(Core.Domain.Enums.WinoApplicationMode.Mail);
|
||||||
WeakReferenceMessenger.Default.Send(message);
|
WeakReferenceMessenger.Default.Send(message);
|
||||||
MainWindow.BringToFront();
|
MainWindow.BringToFront();
|
||||||
}
|
}
|
||||||
@@ -377,7 +417,7 @@ public partial class App : WinoApplication,
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the main window and activates it.
|
/// Creates the main window and activates it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task CreateAndActivateWindow(LaunchActivatedEventArgs args)
|
private async Task CreateAndActivateWindow(Microsoft.UI.Xaml.LaunchActivatedEventArgs? args)
|
||||||
{
|
{
|
||||||
CreateWindow(args);
|
CreateWindow(args);
|
||||||
|
|
||||||
@@ -392,7 +432,7 @@ public partial class App : WinoApplication,
|
|||||||
/// Creates the main window without activating it.
|
/// Creates the main window without activating it.
|
||||||
/// Used for both normal launch and startup task launch (tray only).
|
/// Used for both normal launch and startup task launch (tray only).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void CreateWindow(LaunchActivatedEventArgs args)
|
private void CreateWindow(Microsoft.UI.Xaml.LaunchActivatedEventArgs? args)
|
||||||
{
|
{
|
||||||
LogActivation("Creating main window.");
|
LogActivation("Creating main window.");
|
||||||
|
|
||||||
@@ -404,7 +444,22 @@ public partial class App : WinoApplication,
|
|||||||
if (MainWindow is not IWinoShellWindow shellWindow)
|
if (MainWindow is not IWinoShellWindow shellWindow)
|
||||||
throw new ArgumentException("MainWindow must implement IWinoShellWindow");
|
throw new ArgumentException("MainWindow must implement IWinoShellWindow");
|
||||||
|
|
||||||
shellWindow.HandleAppActivation(args);
|
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
|
||||||
|
|
||||||
|
if (activationArgs.Kind == ExtendedActivationKind.Launch &&
|
||||||
|
activationArgs.Data is ILaunchActivatedEventArgs launchArgs)
|
||||||
|
{
|
||||||
|
shellWindow.HandleAppActivation(launchArgs.Arguments, launchArgs.TileId, Environment.CommandLine);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryResolveActivationMode(activationArgs, out var activationMode))
|
||||||
|
{
|
||||||
|
shellWindow.HandleAppActivation(GetModeLaunchArgument(activationMode));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
shellWindow.HandleAppActivation(args?.Arguments, GetCurrentLaunchTileId(), Environment.CommandLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RegisterRecipients()
|
private void RegisterRecipients()
|
||||||
@@ -622,11 +677,92 @@ public partial class App : WinoApplication,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// For other activation types (Launch, Protocol, etc.), bring window to front
|
if (MainWindow is IWinoShellWindow shellWindow)
|
||||||
|
{
|
||||||
|
if (args.Kind == ExtendedActivationKind.Launch &&
|
||||||
|
args.Data is ILaunchActivatedEventArgs launchArgs)
|
||||||
|
{
|
||||||
|
shellWindow.HandleAppActivation(launchArgs.Arguments, launchArgs.TileId);
|
||||||
|
}
|
||||||
|
else if (TryResolveActivationMode(args, out var redirectedMode))
|
||||||
|
{
|
||||||
|
shellWindow.HandleAppActivation(GetModeLaunchArgument(redirectedMode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bring the existing window to front after handling redirected activation.
|
||||||
MainWindow?.BringToFront();
|
MainWindow?.BringToFront();
|
||||||
MainWindow?.Activate();
|
MainWindow?.Activate();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetModeLaunchArgument(WinoApplicationMode mode)
|
||||||
|
=> mode == WinoApplicationMode.Calendar ? "--mode=calendar" : "--mode=mail";
|
||||||
|
|
||||||
|
private static bool TryResolveActivationMode(AppActivationArguments activationArgs, out WinoApplicationMode mode)
|
||||||
|
{
|
||||||
|
mode = WinoApplicationMode.Mail;
|
||||||
|
|
||||||
|
if (activationArgs.Kind == ExtendedActivationKind.Protocol &&
|
||||||
|
activationArgs.Data is IProtocolActivatedEventArgs protocolArgs)
|
||||||
|
{
|
||||||
|
var scheme = protocolArgs.Uri?.Scheme;
|
||||||
|
|
||||||
|
if (string.Equals(scheme, "webcal", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(scheme, "webcals", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
mode = WinoApplicationMode.Calendar;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(scheme, "mailto", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(scheme, "google.pw.oauth2", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
mode = WinoApplicationMode.Mail;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activationArgs.Kind == ExtendedActivationKind.File &&
|
||||||
|
activationArgs.Data is IFileActivatedEventArgs fileArgs)
|
||||||
|
{
|
||||||
|
var fileItem = fileArgs.Files?.FirstOrDefault();
|
||||||
|
var extension = Path.GetExtension(fileItem?.Name ?? string.Empty);
|
||||||
|
|
||||||
|
if (string.Equals(extension, ".ics", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
mode = WinoApplicationMode.Calendar;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(extension, ".eml", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
mode = WinoApplicationMode.Mail;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activationArgs.Kind == ExtendedActivationKind.Launch &&
|
||||||
|
activationArgs.Data is ILaunchActivatedEventArgs launchArgs)
|
||||||
|
{
|
||||||
|
mode = AppModeActivationResolver.Resolve(launchArgs.Arguments, launchArgs.TileId, null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? GetCurrentLaunchTileId()
|
||||||
|
{
|
||||||
|
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
|
||||||
|
|
||||||
|
if (activationArgs.Kind == ExtendedActivationKind.Launch &&
|
||||||
|
activationArgs.Data is ILaunchActivatedEventArgs launchArgs)
|
||||||
|
{
|
||||||
|
return launchArgs.TileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 186 KiB |
|
After Width: | Height: | Size: 340 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 184 KiB |
|
After Width: | Height: | Size: 336 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 317 KiB |
|
After Width: | Height: | Size: 738 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 738 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 738 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 336 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 263 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 258 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 243 KiB |
|
After Width: | Height: | Size: 623 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 623 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 623 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 21 KiB |