Toast notification navigations and some improvements for list view selection.
This commit is contained in:
@@ -2,13 +2,16 @@
|
||||
using System.Text;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Toolkit.Uwp.Notifications;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.WinUI;
|
||||
using Wino.Core.WinUI.Interfaces;
|
||||
using Wino.Mail.Services;
|
||||
using Wino.Mail.ViewModels;
|
||||
using Wino.Messaging.Client.Accounts;
|
||||
using Wino.Messaging.Server;
|
||||
using Wino.Services;
|
||||
namespace Wino.Mail.WinUI;
|
||||
@@ -22,10 +25,45 @@ public partial class App : WinoApplication, IRecipient<NewMailSynchronizationReq
|
||||
InitializeComponent();
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
ToastNotificationManagerCompat.OnActivated += ToastActivationHandler;
|
||||
|
||||
RegisterRecipients();
|
||||
}
|
||||
|
||||
private async void ToastActivationHandler(ToastNotificationActivatedEventArgsCompat e)
|
||||
{
|
||||
// If we weren't launched by an app, launch our window like normal.
|
||||
// Otherwise if launched by a toast, our OnActivated callback will be triggered.
|
||||
|
||||
var toastArgs = ToastArguments.Parse(e.Argument);
|
||||
|
||||
var mailService = Services.GetRequiredService<IMailService>();
|
||||
var accountService = Services.GetRequiredService<IAccountService>();
|
||||
|
||||
if (Guid.TryParse(toastArgs[Constants.ToastMailUniqueIdKey], out Guid mailItemUniqueId))
|
||||
{
|
||||
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())
|
||||
{
|
||||
MainWindow.BringToFront();
|
||||
}
|
||||
}
|
||||
|
||||
#region Dependency Injection
|
||||
|
||||
|
||||
@@ -80,9 +118,13 @@ public partial class App : WinoApplication, IRecipient<NewMailSynchronizationReq
|
||||
}
|
||||
|
||||
private bool IsStartupTaskLaunch() => AppInstance.GetCurrent().GetActivatedEventArgs()?.Kind == ExtendedActivationKind.StartupTask;
|
||||
public bool IsAppRunning() => MainWindow != null;
|
||||
|
||||
protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
||||
{
|
||||
// If it's toast activation, compat will handle it.
|
||||
if (IsAppRunning()) return;
|
||||
|
||||
// TODO: Check app relaunch mutex before loading anything.
|
||||
|
||||
// Initialize NewThemeService first to get backdrop settings before creating window
|
||||
|
||||
@@ -177,8 +177,22 @@ public partial class WinoListView : Microsoft.UI.Xaml.Controls.ListView
|
||||
if (innerListViewControl != null)
|
||||
{
|
||||
innerListView = innerListViewControl;
|
||||
// TODO: What if it wasn't realized in the thread?
|
||||
|
||||
itemContainer = innerListViewControl.ContainerFromItem(mailItemViewModel) as WinoMailItemViewModelListViewItem;
|
||||
|
||||
// Item thread has been found but container is not realized yet.
|
||||
// This could happen when Sent item passed to navigate for Inbox or vice-versa.
|
||||
// Ideally, we should select the first UniqueId match in the thread in this case.
|
||||
|
||||
if (itemContainer == null)
|
||||
{
|
||||
var realThreadItem = innerListViewControl.Items.Cast<MailItemViewModel>().FirstOrDefault(a => a.UniqueId == mailItemViewModel.MailCopy.UniqueId);
|
||||
|
||||
if (realThreadItem != null)
|
||||
{
|
||||
itemContainer = innerListViewControl.ContainerFromItem(realThreadItem) as WinoMailItemViewModelListViewItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
|
||||
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
IgnorableNamespaces="uap rescap com">
|
||||
|
||||
<!-- Publisher Cache Folders -->
|
||||
<Extensions>
|
||||
@@ -60,6 +62,19 @@
|
||||
Enabled="true"
|
||||
DisplayName="Wino Startup Service" />
|
||||
</uap5:Extension>
|
||||
|
||||
<!-- App notification activation -->
|
||||
<desktop:Extension Category="windows.toastNotificationActivation">
|
||||
<desktop:ToastNotificationActivation ToastActivatorCLSID="72c6d2d0-2538-44fe-a1b1-499f47bb1181" />
|
||||
</desktop:Extension>
|
||||
|
||||
<com:Extension Category="windows.comServer">
|
||||
<com:ComServer>
|
||||
<com:ExeServer Executable="Wino.Mail.WinUI.exe" Arguments="-ToastActivated" DisplayName="Toast activator">
|
||||
<com:Class Id="72c6d2d0-2538-44fe-a1b1-499f47bb1181" DisplayName="Toast activator"/>
|
||||
</com:ExeServer>
|
||||
</com:ComServer>
|
||||
</com:Extension>
|
||||
|
||||
<!-- Protocol activation: mailto -->
|
||||
<uap:Extension Category="windows.protocol">
|
||||
|
||||
@@ -313,13 +313,18 @@ public sealed partial class MailListPage : MailListPageAbstract,
|
||||
|
||||
public async void Receive(SelectMailItemContainerEvent message)
|
||||
{
|
||||
if (message.SelectedMailViewModel == null) return;
|
||||
if (message.MailUniqueId == Guid.Empty) return;
|
||||
|
||||
// Find the item from the collection.
|
||||
// Folder should be initialized already.
|
||||
|
||||
var item = ViewModel.MailCollection.Find(message.MailUniqueId);
|
||||
|
||||
if (item == null) return;
|
||||
|
||||
await DispatcherQueue.EnqueueAsync(async () =>
|
||||
{
|
||||
// MailListView.ClearSelections(message.SelectedMailViewModel, true);
|
||||
|
||||
var collectionContainer = await MailListView.GetItemContainersAsync(message.SelectedMailViewModel);
|
||||
var collectionContainer = await MailListView.GetItemContainersAsync(item);
|
||||
|
||||
if (collectionContainer.Item1 == null && collectionContainer.Item2 == null) return;
|
||||
|
||||
@@ -347,11 +352,7 @@ public sealed partial class MailListPage : MailListPageAbstract,
|
||||
}
|
||||
}
|
||||
|
||||
var listView = collectionContainer.Item3 ?? MailListView;
|
||||
var mailItemViewModelContainer = collectionContainer.Item1;
|
||||
var threadMailItemViewModelContainer = collectionContainer.Item2;
|
||||
|
||||
await WinoClickItemInternalAsync(listView, collectionContainer.Item1?.Item ?? null);
|
||||
await WinoClickItemInternalAsync(item, true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -554,7 +555,7 @@ public sealed partial class MailListPage : MailListPageAbstract,
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WinoClickItemInternalAsync(WinoListView listView, object? clickedItem)
|
||||
private async Task WinoClickItemInternalAsync(object? clickedItem, bool selectExpandThread = false)
|
||||
{
|
||||
if (clickedItem == null) return;
|
||||
|
||||
@@ -621,29 +622,40 @@ public sealed partial class MailListPage : MailListPageAbstract,
|
||||
if (clickedItem is ThreadMailItemViewModel clickedThread)
|
||||
{
|
||||
bool wasThreadSelected = clickedThread.IsSelected;
|
||||
bool wasThreadExpanded = clickedThread.IsThreadExpanded;
|
||||
|
||||
// Check if any child in this thread is already selected (e.g., from notification click)
|
||||
var alreadySelectedChild = clickedThread.ThreadEmails.FirstOrDefault(e => e.IsSelected);
|
||||
|
||||
// Reset everything first (exclusive selection scenario)
|
||||
await ViewModel.MailCollection.UnselectAllAsync();
|
||||
await CollapseAllThreadsExceptAsync(clickedThread);
|
||||
|
||||
if (wasThreadSelected)
|
||||
if (wasThreadSelected && wasThreadExpanded)
|
||||
{
|
||||
// Toggle off -> leave nothing selected (all unselected, thread collapsed)
|
||||
clickedThread.IsThreadExpanded = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Select thread + first child only
|
||||
// Select thread header
|
||||
clickedThread.IsSelected = true;
|
||||
var firstChild = clickedThread.ThreadEmails.FirstOrDefault();
|
||||
if (firstChild != null)
|
||||
|
||||
// If a child was already selected (e.g., from notification), keep that selection
|
||||
// Otherwise, select the first child
|
||||
if (alreadySelectedChild != null)
|
||||
{
|
||||
// Ensure only first child selected
|
||||
foreach (var child in clickedThread.ThreadEmails)
|
||||
alreadySelectedChild.IsSelected = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var firstChild = clickedThread.ThreadEmails.FirstOrDefault();
|
||||
if (firstChild != null)
|
||||
{
|
||||
child.IsSelected = child == firstChild;
|
||||
firstChild.IsSelected = true;
|
||||
}
|
||||
}
|
||||
|
||||
clickedThread.IsThreadExpanded = true; // Show contents of active thread
|
||||
}
|
||||
else if (clickedItem is MailItemViewModel clickedMail)
|
||||
@@ -695,6 +707,13 @@ public sealed partial class MailListPage : MailListPageAbstract,
|
||||
await ViewModel.MailCollection.UnselectAllAsync();
|
||||
await ViewModel.MailCollection.CollapseAllThreadsAsync();
|
||||
|
||||
if (parentThread != null && selectExpandThread)
|
||||
{
|
||||
// We're clicking an item inside a thread; select & expand the thread header as well.
|
||||
parentThread.IsSelected = true;
|
||||
parentThread.IsThreadExpanded = true;
|
||||
}
|
||||
|
||||
if (!wasSelected)
|
||||
{
|
||||
clickedMail.IsSelected = true; // Toggle on
|
||||
@@ -706,6 +725,6 @@ public sealed partial class MailListPage : MailListPageAbstract,
|
||||
{
|
||||
if (sender is not WinoListView listView) return;
|
||||
|
||||
await WinoClickItemInternalAsync(listView, e.ClickedItem);
|
||||
await WinoClickItemInternalAsync(e.ClickedItem);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user