Wino Calendar app entry. (#819)

* Double app entry.

* New icon for sys tray
This commit is contained in:
Burak Kaan Köse
2026-02-22 15:13:39 +01:00
committed by GitHub
parent 17ca32c537
commit 311b3c77c8
171 changed files with 295 additions and 65 deletions
@@ -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);
}
+146 -10
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
@@ -11,6 +12,7 @@ using Microsoft.UI.Xaml;
using Microsoft.Windows.AppLifecycle;
using Microsoft.Windows.AppNotifications;
using MimeKit.Cryptography;
using Windows.ApplicationModel.Activation;
using Wino.Calendar.ViewModels;
using Wino.Calendar.ViewModels.Interfaces;
using Wino.Core;
@@ -22,11 +24,12 @@ using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Synchronization;
using Wino.Mail.Services;
using Wino.Mail.ViewModels;
using Wino.Mail.WinUI.Activation;
using Wino.Mail.WinUI.Interfaces;
using Wino.Mail.WinUI.Services;
using Wino.Messaging.UI;
using Wino.Messaging.Client.Accounts;
using Wino.Messaging.Server;
using Wino.Messaging.UI;
using Wino.Services;
namespace Wino.Mail.WinUI;
@@ -133,11 +136,10 @@ public partial class App : WinoApplication,
{
base.OnLaunched(args);
AppNotificationManager notificationManager = AppNotificationManager.Default;
notificationManager.NotificationInvoked -= AppNotificationInvoked;
notificationManager.NotificationInvoked += AppNotificationInvoked;
notificationManager.Register();
if (ShouldRegisterAppNotifications(args))
{
TryRegisterAppNotifications();
}
// Initialize required services regardless of launch activation type.
// 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)
=> 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>
/// Handles toast notification activation scenarios.
/// </summary>
@@ -256,6 +294,7 @@ 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).ConfigureAwait(false);
if (account == null) return;
@@ -278,6 +317,7 @@ public partial class App : WinoApplication,
else
{
// App is already running - send message and bring window to front.
navigationService.ChangeApplicationMode(Core.Domain.Enums.WinoApplicationMode.Mail);
WeakReferenceMessenger.Default.Send(message);
MainWindow.BringToFront();
}
@@ -377,7 +417,7 @@ public partial class App : WinoApplication,
/// <summary>
/// Creates the main window and activates it.
/// </summary>
private async Task CreateAndActivateWindow(LaunchActivatedEventArgs args)
private async Task CreateAndActivateWindow(Microsoft.UI.Xaml.LaunchActivatedEventArgs? args)
{
CreateWindow(args);
@@ -392,7 +432,7 @@ public partial class App : WinoApplication,
/// Creates the main window without activating it.
/// Used for both normal launch and startup task launch (tray only).
/// </summary>
private void CreateWindow(LaunchActivatedEventArgs args)
private void CreateWindow(Microsoft.UI.Xaml.LaunchActivatedEventArgs? args)
{
LogActivation("Creating main window.");
@@ -404,7 +444,22 @@ public partial class App : WinoApplication,
if (MainWindow is not IWinoShellWindow shellWindow)
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()
@@ -622,11 +677,92 @@ public partial class App : WinoApplication,
}
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?.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;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 738 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 738 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 738 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Some files were not shown because too many files have changed in this diff Show More