Implemented initial version for popping out window for rendering and compose pages

This commit is contained in:
Burak Kaan Köse
2026-04-11 01:04:59 +02:00
parent d5c121ce24
commit 4cb08f0a98
22 changed files with 628 additions and 79 deletions
+186 -2
View File
@@ -28,8 +28,10 @@ using Wino.Mail.ViewModels.Messages;
using Wino.Mail.WinUI;
using Wino.Mail.WinUI.Controls.ListView;
using Wino.Mail.WinUI.Extensions;
using Wino.Mail.WinUI.Helpers;
using Wino.Mail.WinUI.Interfaces;
using Wino.Mail.WinUI.Models;
using Wino.Mail.WinUI.Services;
using Wino.MenuFlyouts.Context;
using Wino.Messaging.Client.Mails;
using Wino.Views.Abstract;
@@ -48,12 +50,16 @@ public sealed partial class MailListPage : MailListPageAbstract,
IRecipient<ActiveMailItemChangedEvent>,
IRecipient<SelectMailItemContainerEvent>,
IRecipient<DisposeRenderingFrameRequested>,
IHostedPopoutSource,
ITitleBarSearchHost
{
private const double RENDERING_COLUMN_MIN_WIDTH = 375;
private const int SELECTION_SETTLE_DELAY_MS = 120;
private const int RENDERING_FRAME_RELEASE_DELAY_MS = 2000;
private int _idleNavigationRequestVersion = 0;
private IPopoutClient? _activePopoutClient;
private readonly Dictionary<FrameworkElement, HostedContentPopoutWindow> _hostedPopoutWindows = [];
private PendingHostedPopoutNavigation? _pendingHostedPopoutNavigation;
private IStatePersistanceService StatePersistenceService { get; } = WinoApplication.Current.Services.GetService<IStatePersistanceService>() ?? throw new Exception($"Can't resolve {nameof(KeyPressService)}");
private IKeyPressService KeyPressService { get; } = WinoApplication.Current.Services.GetService<IKeyPressService>() ?? throw new Exception($"Can't resolve {nameof(KeyPressService)}");
@@ -69,6 +75,7 @@ public sealed partial class MailListPage : MailListPageAbstract,
public MailListPage()
{
InitializeComponent();
RenderingFrame.Navigated += RenderingFrame_Navigated;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
@@ -101,6 +108,7 @@ public sealed partial class MailListPage : MailListPageAbstract,
base.OnNavigatedFrom(e);
InvalidatePendingIdleNavigation();
DetachPopoutClient();
this.Bindings.StopTracking();
@@ -325,7 +333,10 @@ public sealed partial class MailListPage : MailListPageAbstract,
PrepareRenderingPageWebViewTransition();
// Dispose existing HTML content from rendering page webview.
WeakReferenceMessenger.Default.Send(new CancelRenderingContentRequested());
if (RenderingFrame.Content is MailRenderingPage renderingPage)
{
_ = renderingPage.ClearRenderedContentAsync();
}
}
else if (IsComposingPageActive())
{
@@ -359,6 +370,55 @@ public sealed partial class MailListPage : MailListPageAbstract,
private bool IsRenderingPageActive() => RenderingFrame.Content is MailRenderingPage;
private bool IsComposingPageActive() => RenderingFrame.Content is ComposePage;
private void RenderingFrame_Navigated(object sender, NavigationEventArgs e)
{
AttachPopoutClient(RenderingFrame.Content as IPopoutClient);
if (_pendingHostedPopoutNavigation != null
&& TryGetPendingHostedPopoutTarget(RenderingFrame.Content, _pendingHostedPopoutNavigation, out var hostedContent))
{
_ = ContinuePendingHostedPopoutNavigationAsync(hostedContent, _pendingHostedPopoutNavigation);
}
}
private void AttachPopoutClient(IPopoutClient? client)
{
if (ReferenceEquals(_activePopoutClient, client))
return;
DetachPopoutClient();
_activePopoutClient = client;
if (_activePopoutClient != null)
{
_activePopoutClient.PopOutRequested += ActivePopoutClient_PopOutRequested;
_activePopoutClient.HostActionRequested += ActivePopoutClient_HostActionRequested;
}
}
private void DetachPopoutClient()
{
if (_activePopoutClient != null)
{
_activePopoutClient.PopOutRequested -= ActivePopoutClient_PopOutRequested;
_activePopoutClient.HostActionRequested -= ActivePopoutClient_HostActionRequested;
_activePopoutClient = null;
}
}
private async void ActivePopoutClient_PopOutRequested(object? sender, PopOutRequestedEventArgs e)
{
await HostedContentPopoutCoordinator.PopOutCurrentContentAsync(this);
}
private void ActivePopoutClient_HostActionRequested(object? sender, PopoutHostActionRequestedEventArgs e)
{
if (sender is FrameworkElement content)
{
HandleHostedClientAction(content, e);
}
}
private void InvalidatePendingIdleNavigation()
{
unchecked
@@ -378,7 +438,10 @@ public sealed partial class MailListPage : MailListPageAbstract,
if (IsRenderingPageActive())
{
WeakReferenceMessenger.Default.Send(new CancelRenderingContentRequested());
if (RenderingFrame.Content is MailRenderingPage renderingPage)
{
_ = renderingPage.ClearRenderedContentAsync();
}
}
await Task.Delay(RENDERING_FRAME_RELEASE_DELAY_MS);
@@ -926,4 +989,125 @@ public sealed partial class MailListPage : MailListPageAbstract,
return Task.CompletedTask;
}
public bool CanPopOutCurrentContent()
{
return RenderingFrame.Content is FrameworkElement
&& RenderingFrame.Content is IPopoutClient client
&& client.SupportsPopOut;
}
public FrameworkElement? GetCurrentHostedContent()
{
return RenderingFrame.Content as FrameworkElement;
}
public HostedPopoutDescriptor CreatePopoutDescriptor(IPopoutClient client)
{
return client.GetPopoutDescriptor();
}
public FrameworkElement DetachHostedContent()
{
if (RenderingFrame.Content is not FrameworkElement content)
throw new InvalidOperationException("RenderingFrame does not host detachable content.");
InvalidatePendingIdleNavigation();
DetachPopoutClient();
RenderingFrame.Content = null;
ViewModel.NavigationService.Navigate(WinoPage.IdlePage, null, NavigationReferenceFrame.RenderingFrame, NavigationTransitionType.None);
return content;
}
public void OnHostedContentPoppedOut(FrameworkElement content, HostedContentPopoutWindow window, HostedPopoutDescriptor descriptor)
{
if (content is IPopoutClient client)
{
client.HostActionRequested -= ActivePopoutClient_HostActionRequested;
client.HostActionRequested += ActivePopoutClient_HostActionRequested;
}
_hostedPopoutWindows[content] = window;
_ = ViewModel.MailCollection.UnselectAllAsync();
UpdateAdaptiveness();
}
public void OnHostedPopoutClosed(FrameworkElement content, HostedPopoutDescriptor descriptor)
{
if (_hostedPopoutWindows.Remove(content) && content is IPopoutClient hostedClient)
{
hostedClient.HostActionRequested -= ActivePopoutClient_HostActionRequested;
}
if (_pendingHostedPopoutNavigation?.SourceContent == content)
{
_pendingHostedPopoutNavigation = null;
}
DispatcherQueue.TryEnqueue(() =>
{
if (content is IPopoutClient client)
{
client.OnPopoutStateChanged(false);
}
WindowCleanupHelper.CleanupObject(content);
});
}
private void HandleHostedClientAction(FrameworkElement content, PopoutHostActionRequestedEventArgs args)
{
if (!_hostedPopoutWindows.TryGetValue(content, out var hostedWindow))
return;
switch (args.ActionKind)
{
case PopoutHostActionKind.CloseHostedInstance:
hostedWindow.Close();
break;
case PopoutHostActionKind.PopOutNextNavigation when args.TargetPageType != null:
_pendingHostedPopoutNavigation = new PendingHostedPopoutNavigation(content, hostedWindow, args.TargetPageType, args.TargetMailUniqueId);
break;
}
}
private static bool TryGetPendingHostedPopoutTarget(object? currentContent, PendingHostedPopoutNavigation pendingHostedNavigation, out FrameworkElement hostedContent)
{
hostedContent = null!;
if (currentContent is not FrameworkElement currentFrameworkElement || currentFrameworkElement.GetType() != pendingHostedNavigation.TargetPageType)
return false;
if (pendingHostedNavigation.TargetMailUniqueId.HasValue
&& currentFrameworkElement is ComposePage composePage
&& composePage.ViewModel.CurrentMailDraftItem?.MailCopy?.UniqueId != pendingHostedNavigation.TargetMailUniqueId.Value)
{
return false;
}
hostedContent = currentFrameworkElement;
return true;
}
private async Task ContinuePendingHostedPopoutNavigationAsync(FrameworkElement content, PendingHostedPopoutNavigation pendingHostedNavigation)
{
if (!ReferenceEquals(_pendingHostedPopoutNavigation, pendingHostedNavigation))
return;
_pendingHostedPopoutNavigation = null;
var didPopOut = await HostedContentPopoutCoordinator.PopOutCurrentContentAsync(this);
if (didPopOut)
{
pendingHostedNavigation.SourceWindow.Close();
}
}
private sealed record PendingHostedPopoutNavigation(
FrameworkElement SourceContent,
HostedContentPopoutWindow SourceWindow,
Type TargetPageType,
Guid? TargetMailUniqueId);
}