Implemented initial version for popping out window for rendering and compose pages
This commit is contained in:
@@ -58,6 +58,7 @@
|
|||||||
"Buttons_Allow": "Allow",
|
"Buttons_Allow": "Allow",
|
||||||
"Buttons_Apply": "Apply",
|
"Buttons_Apply": "Apply",
|
||||||
"Buttons_ApplyTheme": "Apply Theme",
|
"Buttons_ApplyTheme": "Apply Theme",
|
||||||
|
"Buttons_PopOut": "Pop out",
|
||||||
"Buttons_Browse": "Browse",
|
"Buttons_Browse": "Browse",
|
||||||
"Buttons_Cancel": "Cancel",
|
"Buttons_Cancel": "Cancel",
|
||||||
"Buttons_Close": "Close",
|
"Buttons_Close": "Close",
|
||||||
|
|||||||
@@ -23,21 +23,22 @@ using Wino.Core.Domain.Models.Navigation;
|
|||||||
using Wino.Core.Extensions;
|
using Wino.Core.Extensions;
|
||||||
using Wino.Core.Services;
|
using Wino.Core.Services;
|
||||||
using Wino.Mail.ViewModels.Data;
|
using Wino.Mail.ViewModels.Data;
|
||||||
using Wino.Mail.ViewModels.Messages;
|
|
||||||
using Wino.Messaging.Client.Mails;
|
using Wino.Messaging.Client.Mails;
|
||||||
using Wino.Messaging.UI;
|
using Wino.Messaging.UI;
|
||||||
|
|
||||||
namespace Wino.Mail.ViewModels;
|
namespace Wino.Mail.ViewModels;
|
||||||
|
|
||||||
public partial class ComposePageViewModel : MailBaseViewModel,
|
public partial class ComposePageViewModel : MailBaseViewModel,
|
||||||
IRecipient<ReaderItemRefreshRequestedEvent>,
|
|
||||||
IRecipient<SynchronizationActionsAdded>,
|
IRecipient<SynchronizationActionsAdded>,
|
||||||
IRecipient<SynchronizationActionsCompleted>,
|
IRecipient<SynchronizationActionsCompleted>,
|
||||||
IRecipient<AccountSynchronizerStateChanged>
|
IRecipient<AccountSynchronizerStateChanged>
|
||||||
{
|
{
|
||||||
|
public event EventHandler CloseRequested;
|
||||||
|
|
||||||
private static readonly TimeSpan LocalDraftRetryGracePeriod = TimeSpan.FromSeconds(15);
|
private static readonly TimeSpan LocalDraftRetryGracePeriod = TimeSpan.FromSeconds(15);
|
||||||
|
|
||||||
public Func<Task<string>> GetHTMLBodyFunction;
|
public Func<Task<string>> GetHTMLBodyFunction;
|
||||||
|
public Func<string, Task> RenderHtmlBodyAsyncFunc { get; set; }
|
||||||
|
|
||||||
public override async Task KeyboardShortcutHook(KeyboardShortcutTriggerDetails args)
|
public override async Task KeyboardShortcutHook(KeyboardShortcutTriggerDetails args)
|
||||||
{
|
{
|
||||||
@@ -529,9 +530,9 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void Receive(ReaderItemRefreshRequestedEvent message)
|
public async Task RefreshDraftAsync(MailItemViewModel draftMailItemViewModel)
|
||||||
{
|
{
|
||||||
if (message.MailItemViewModel == null || !message.MailItemViewModel.IsDraft) return;
|
if (draftMailItemViewModel == null || !draftMailItemViewModel.IsDraft) return;
|
||||||
|
|
||||||
// Save current draft before switching.
|
// Save current draft before switching.
|
||||||
await UpdateMimeChangesAsync();
|
await UpdateMimeChangesAsync();
|
||||||
@@ -542,7 +543,7 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
IncludedAttachments.Clear();
|
IncludedAttachments.Clear();
|
||||||
|
|
||||||
// Set the new draft item and prepare it.
|
// Set the new draft item and prepare it.
|
||||||
CurrentMailDraftItem = message.MailItemViewModel;
|
CurrentMailDraftItem = draftMailItemViewModel;
|
||||||
await UpdatePendingOperationStateAsync();
|
await UpdatePendingOperationStateAsync();
|
||||||
await LoadEmailTemplatesAsync();
|
await LoadEmailTemplatesAsync();
|
||||||
await TryPrepareComposeAsync(true);
|
await TryPrepareComposeAsync(true);
|
||||||
@@ -591,7 +592,6 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
{
|
{
|
||||||
base.RegisterRecipients();
|
base.RegisterRecipients();
|
||||||
|
|
||||||
Messenger.Register<ReaderItemRefreshRequestedEvent>(this);
|
|
||||||
Messenger.Register<SynchronizationActionsAdded>(this);
|
Messenger.Register<SynchronizationActionsAdded>(this);
|
||||||
Messenger.Register<SynchronizationActionsCompleted>(this);
|
Messenger.Register<SynchronizationActionsCompleted>(this);
|
||||||
Messenger.Register<AccountSynchronizerStateChanged>(this);
|
Messenger.Register<AccountSynchronizerStateChanged>(this);
|
||||||
@@ -601,7 +601,6 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
{
|
{
|
||||||
base.UnregisterRecipients();
|
base.UnregisterRecipients();
|
||||||
|
|
||||||
Messenger.Unregister<ReaderItemRefreshRequestedEvent>(this);
|
|
||||||
Messenger.Unregister<SynchronizationActionsAdded>(this);
|
Messenger.Unregister<SynchronizationActionsAdded>(this);
|
||||||
Messenger.Unregister<SynchronizationActionsCompleted>(this);
|
Messenger.Unregister<SynchronizationActionsCompleted>(this);
|
||||||
Messenger.Unregister<AccountSynchronizerStateChanged>(this);
|
Messenger.Unregister<AccountSynchronizerStateChanged>(this);
|
||||||
@@ -754,9 +753,12 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
IsCCBCCVisible = true;
|
IsCCBCCVisible = true;
|
||||||
|
|
||||||
Subject = replyingMime.Subject;
|
Subject = replyingMime.Subject;
|
||||||
|
|
||||||
Messenger.Send(new CreateNewComposeMailRequested(renderModel));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (RenderHtmlBodyAsyncFunc != null)
|
||||||
|
{
|
||||||
|
await ExecuteUIThread(async () => await RenderHtmlBodyAsyncFunc(renderModel.RenderHtml));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadAttachments()
|
private void LoadAttachments()
|
||||||
@@ -891,6 +893,19 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override async void OnMailRemoved(MailCopy removedMail, EntityUpdateSource source)
|
||||||
|
{
|
||||||
|
base.OnMailRemoved(removedMail, source);
|
||||||
|
|
||||||
|
if (CurrentMailDraftItem?.MailCopy == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (CurrentMailDraftItem.MailCopy.UniqueId != removedMail.UniqueId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await ExecuteUIThread(() => CloseRequested?.Invoke(this, EventArgs.Empty));
|
||||||
|
}
|
||||||
|
|
||||||
private void NotifyComposeActionStateChanged()
|
private void NotifyComposeActionStateChanged()
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(IsLocalDraft));
|
OnPropertyChanged(nameof(IsLocalDraft));
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ using Wino.Core.Domain.Models.Printing;
|
|||||||
using Wino.Core.Domain.Models.Reader;
|
using Wino.Core.Domain.Models.Reader;
|
||||||
using Wino.Core.Services;
|
using Wino.Core.Services;
|
||||||
using Wino.Mail.ViewModels.Data;
|
using Wino.Mail.ViewModels.Data;
|
||||||
using Wino.Mail.ViewModels.Messages;
|
using Wino.Mail.ViewModels.Models;
|
||||||
using Wino.Messaging.Client.Mails;
|
using Wino.Messaging.Client.Mails;
|
||||||
using Wino.Messaging.UI;
|
using Wino.Messaging.UI;
|
||||||
using IMailService = Wino.Core.Domain.Interfaces.IMailService;
|
using IMailService = Wino.Core.Domain.Interfaces.IMailService;
|
||||||
@@ -34,10 +34,12 @@ using IMailService = Wino.Core.Domain.Interfaces.IMailService;
|
|||||||
namespace Wino.Mail.ViewModels;
|
namespace Wino.Mail.ViewModels;
|
||||||
|
|
||||||
public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
||||||
IRecipient<ReaderItemRefreshRequestedEvent>,
|
|
||||||
IRecipient<ThumbnailAdded>,
|
IRecipient<ThumbnailAdded>,
|
||||||
ITransferProgress // For listening IMAP message download progress.
|
ITransferProgress // For listening IMAP message download progress.
|
||||||
{
|
{
|
||||||
|
public event EventHandler CloseRequested;
|
||||||
|
public event EventHandler<ComposeDraftRequestedEventArgs> ComposeRequested;
|
||||||
|
|
||||||
private readonly IMailDialogService _dialogService;
|
private readonly IMailDialogService _dialogService;
|
||||||
private readonly IUnderlyingThemeService _underlyingThemeService;
|
private readonly IUnderlyingThemeService _underlyingThemeService;
|
||||||
|
|
||||||
@@ -58,8 +60,9 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
// Func to get WebView2 to save current HTML as PDF to given location.
|
// Func to get WebView2 to save current HTML as PDF to given location.
|
||||||
// Used in 'Save as' and 'Print' functionality.
|
// Used in 'Save as' and 'Print' functionality.
|
||||||
public Func<string, Task<bool>> SaveHTMLasPDFFunc { get; set; }
|
public Func<string, Task<bool>> SaveHTMLasPDFFunc { get; set; }
|
||||||
|
|
||||||
public Func<WebView2PrintSettingsModel, Task<PrintingResult>> DirectPrintFuncAsync { get; set; }
|
public Func<WebView2PrintSettingsModel, Task<PrintingResult>> DirectPrintFuncAsync { get; set; }
|
||||||
|
public Func<string, Task> RenderHtmlAsyncFunc { get; set; }
|
||||||
|
public Func<Task> ClearRenderedHtmlAsyncFunc { get; set; }
|
||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
|
|
||||||
@@ -319,6 +322,7 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
var draftPreparationRequest = new DraftPreparationRequest(initializedMailItemViewModel.MailCopy.AssignedAccount, draftMailCopy, draftBase64MimeMessage, draftOptions.Reason, initializedMailItemViewModel.MailCopy);
|
var draftPreparationRequest = new DraftPreparationRequest(initializedMailItemViewModel.MailCopy.AssignedAccount, draftMailCopy, draftBase64MimeMessage, draftOptions.Reason, initializedMailItemViewModel.MailCopy);
|
||||||
|
|
||||||
await _requestDelegator.ExecuteAsync(draftPreparationRequest);
|
await _requestDelegator.ExecuteAsync(draftPreparationRequest);
|
||||||
|
ComposeRequested?.Invoke(this, new ComposeDraftRequestedEventArgs(draftMailCopy.UniqueId));
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (initializedMailItemViewModel != null)
|
else if (initializedMailItemViewModel != null)
|
||||||
@@ -362,8 +366,10 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
initializedMimeMessageInformation = null;
|
initializedMimeMessageInformation = null;
|
||||||
CurrentMailItemDisplayInformation = null;
|
CurrentMailItemDisplayInformation = null;
|
||||||
|
|
||||||
// Dispose existing content first.
|
if (ClearRenderedHtmlAsyncFunc != null)
|
||||||
Messenger.Send(new CancelRenderingContentRequested());
|
{
|
||||||
|
await ExecuteUIThread(async () => await ClearRenderedHtmlAsyncFunc());
|
||||||
|
}
|
||||||
|
|
||||||
// This page can be accessed for 2 purposes.
|
// This page can be accessed for 2 purposes.
|
||||||
// 1. Rendering a mail item when the user selects.
|
// 1. Rendering a mail item when the user selects.
|
||||||
@@ -509,8 +515,6 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
|
|
||||||
CurrentRenderModel = _mimeFileService.GetMailRenderModel(message, messagePath, renderingOptions);
|
CurrentRenderModel = _mimeFileService.GetMailRenderModel(message, messagePath, renderingOptions);
|
||||||
|
|
||||||
Messenger.Send(new HtmlRenderingRequested(CurrentRenderModel.RenderHtml));
|
|
||||||
|
|
||||||
foreach (var attachment in CurrentRenderModel.Attachments)
|
foreach (var attachment in CurrentRenderModel.Attachments)
|
||||||
{
|
{
|
||||||
Attachments.Add(new MailAttachmentViewModel(attachment));
|
Attachments.Add(new MailAttachmentViewModel(attachment));
|
||||||
@@ -520,6 +524,11 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
|
|
||||||
StatePersistenceService.IsReadingMail = true;
|
StatePersistenceService.IsReadingMail = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (RenderHtmlAsyncFunc != null)
|
||||||
|
{
|
||||||
|
await ExecuteUIThread(async () => await RenderHtmlAsyncFunc(CurrentRenderModel.RenderHtml));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<AccountContactViewModel>> GetAccountContacts(InternetAddressList internetAddresses)
|
private async Task<List<AccountContactViewModel>> GetAccountContacts(InternetAddressList internetAddresses)
|
||||||
@@ -658,6 +667,19 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
await ExecuteUIThread(() => { InitializeCommandBarItems(); });
|
await ExecuteUIThread(() => { InitializeCommandBarItems(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override async void OnMailRemoved(MailCopy removedMail, EntityUpdateSource source)
|
||||||
|
{
|
||||||
|
base.OnMailRemoved(removedMail, source);
|
||||||
|
|
||||||
|
if (initializedMailItemViewModel?.MailCopy == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (initializedMailItemViewModel.MailCopy.UniqueId != removedMail.UniqueId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await ExecuteUIThread(() => CloseRequested?.Invoke(this, EventArgs.Empty));
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task OpenAttachmentAsync(MailAttachmentViewModel attachmentViewModel)
|
private async Task OpenAttachmentAsync(MailAttachmentViewModel attachmentViewModel)
|
||||||
{
|
{
|
||||||
@@ -794,13 +816,13 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
// For upload.
|
// For upload.
|
||||||
void ITransferProgress.Report(long bytesTransferred) { }
|
void ITransferProgress.Report(long bytesTransferred) { }
|
||||||
|
|
||||||
public async void Receive(ReaderItemRefreshRequestedEvent message)
|
public async Task RefreshMailItemAsync(MailItemViewModel mailItemViewModel)
|
||||||
{
|
{
|
||||||
if (message.MailItemViewModel == null || message.MailItemViewModel.IsDraft) return;
|
if (mailItemViewModel == null || mailItemViewModel.IsDraft) return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await RenderAsync(message.MailItemViewModel, renderCancellationTokenSource.Token);
|
await RenderAsync(mailItemViewModel, renderCancellationTokenSource.Token);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@@ -907,7 +929,6 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
{
|
{
|
||||||
base.RegisterRecipients();
|
base.RegisterRecipients();
|
||||||
|
|
||||||
Messenger.Register<ReaderItemRefreshRequestedEvent>(this);
|
|
||||||
Messenger.Register<ThumbnailAdded>(this);
|
Messenger.Register<ThumbnailAdded>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -915,7 +936,6 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
{
|
{
|
||||||
base.UnregisterRecipients();
|
base.UnregisterRecipients();
|
||||||
|
|
||||||
Messenger.Unregister<ReaderItemRefreshRequestedEvent>(this);
|
|
||||||
Messenger.Unregister<ThumbnailAdded>(this);
|
Messenger.Unregister<ThumbnailAdded>(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Wino.Mail.ViewModels.Models;
|
||||||
|
|
||||||
|
public sealed class ComposeDraftRequestedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public ComposeDraftRequestedEventArgs(Guid draftUniqueId)
|
||||||
|
{
|
||||||
|
DraftUniqueId = draftUniqueId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid DraftUniqueId { get; }
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ public sealed partial class OperationCommandBar : CommandBar
|
|||||||
private const string MailOperationTemplateKey = "OperationCommandBarMailOperationTemplate";
|
private const string MailOperationTemplateKey = "OperationCommandBarMailOperationTemplate";
|
||||||
private const string FolderOperationTemplateKey = "OperationCommandBarFolderOperationTemplate";
|
private const string FolderOperationTemplateKey = "OperationCommandBarFolderOperationTemplate";
|
||||||
private const string AIActionsTemplateKey = "OperationCommandBarAIActionsTemplate";
|
private const string AIActionsTemplateKey = "OperationCommandBarAIActionsTemplate";
|
||||||
|
private const string PopOutTemplateKey = "OperationCommandBarThemeToggleTemplate";
|
||||||
private const string ThemeToggleTemplateKey = "OperationCommandBarThemeToggleTemplate";
|
private const string ThemeToggleTemplateKey = "OperationCommandBarThemeToggleTemplate";
|
||||||
private const string SeparatorTemplateKey = "OperationCommandBarSeparatorTemplate";
|
private const string SeparatorTemplateKey = "OperationCommandBarSeparatorTemplate";
|
||||||
|
|
||||||
@@ -47,7 +48,11 @@ public sealed partial class OperationCommandBar : CommandBar
|
|||||||
[GeneratedDependencyProperty]
|
[GeneratedDependencyProperty]
|
||||||
public partial bool IsEditorThemeToggleVisible { get; set; }
|
public partial bool IsEditorThemeToggleVisible { get; set; }
|
||||||
|
|
||||||
|
[GeneratedDependencyProperty]
|
||||||
|
public partial bool IsPopOutButtonVisible { get; set; }
|
||||||
|
|
||||||
public event EventHandler<bool>? AIActionsEnabledChanged;
|
public event EventHandler<bool>? AIActionsEnabledChanged;
|
||||||
|
public event EventHandler? PopOutClicked;
|
||||||
|
|
||||||
public OperationCommandBar()
|
public OperationCommandBar()
|
||||||
{
|
{
|
||||||
@@ -58,7 +63,6 @@ public sealed partial class OperationCommandBar : CommandBar
|
|||||||
OverflowButtonVisibility = CommandBarOverflowButtonVisibility.Auto;
|
OverflowButtonVisibility = CommandBarOverflowButtonVisibility.Auto;
|
||||||
|
|
||||||
Loaded += OnLoaded;
|
Loaded += OnLoaded;
|
||||||
Unloaded += OnUnloaded;
|
|
||||||
DynamicOverflowItemsChanging += OperationCommandBar_DynamicOverflowItemsChanging;
|
DynamicOverflowItemsChanging += OperationCommandBar_DynamicOverflowItemsChanging;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,14 +104,14 @@ public sealed partial class OperationCommandBar : CommandBar
|
|||||||
RefreshCommands();
|
RefreshCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
partial void OnIsPopOutButtonVisibleChanged(bool newValue)
|
||||||
{
|
{
|
||||||
RefreshCommands();
|
RefreshCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUnloaded(object sender, RoutedEventArgs e)
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
ClearGeneratedCommands();
|
RefreshCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OperationCommandBar_DynamicOverflowItemsChanging(CommandBar sender, DynamicOverflowItemsChangingEventArgs args)
|
private void OperationCommandBar_DynamicOverflowItemsChanging(CommandBar sender, DynamicOverflowItemsChangingEventArgs args)
|
||||||
@@ -216,6 +220,11 @@ public sealed partial class OperationCommandBar : CommandBar
|
|||||||
PrimaryCommands.Add(CreateAIActionsToggleButton());
|
PrimaryCommands.Add(CreateAIActionsToggleButton());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsPopOutButtonVisible)
|
||||||
|
{
|
||||||
|
PrimaryCommands.Add(CreatePopOutButton());
|
||||||
|
}
|
||||||
|
|
||||||
if (IsEditorThemeToggleVisible)
|
if (IsEditorThemeToggleVisible)
|
||||||
{
|
{
|
||||||
PrimaryCommands.Add(CreateThemeToggleButton());
|
PrimaryCommands.Add(CreateThemeToggleButton());
|
||||||
@@ -266,6 +275,7 @@ public sealed partial class OperationCommandBar : CommandBar
|
|||||||
case AppBarButton button:
|
case AppBarButton button:
|
||||||
button.Click -= OperationButton_Click;
|
button.Click -= OperationButton_Click;
|
||||||
button.Click -= ThemeButton_Click;
|
button.Click -= ThemeButton_Click;
|
||||||
|
button.Click -= PopOutButton_Click;
|
||||||
break;
|
break;
|
||||||
case AppBarToggleButton toggleButton:
|
case AppBarToggleButton toggleButton:
|
||||||
toggleButton.ClearValue(AppBarToggleButton.IsCheckedProperty);
|
toggleButton.ClearValue(AppBarToggleButton.IsCheckedProperty);
|
||||||
@@ -356,6 +366,16 @@ public sealed partial class OperationCommandBar : CommandBar
|
|||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AppBarButton CreatePopOutButton()
|
||||||
|
{
|
||||||
|
var button = (AppBarButton)LoadCommandBarElementTemplate(
|
||||||
|
PopOutTemplateKey,
|
||||||
|
new OperationCommandBarThemeItemViewModel(Translator.Buttons_PopOut, WinoIconGlyph.OpenInNewWindow));
|
||||||
|
|
||||||
|
button.Click += PopOutButton_Click;
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
private void OperationButton_Click(object sender, RoutedEventArgs e)
|
private void OperationButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is AppBarButton button && button.Tag is IMenuOperation operation)
|
if (sender is AppBarButton button && button.Tag is IMenuOperation operation)
|
||||||
@@ -369,6 +389,11 @@ public sealed partial class OperationCommandBar : CommandBar
|
|||||||
IsEditorThemeDark = !IsEditorThemeDark;
|
IsEditorThemeDark = !IsEditorThemeDark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void PopOutButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
PopOutClicked?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
private object? FindTemplateResource(string key)
|
private object? FindTemplateResource(string key)
|
||||||
{
|
{
|
||||||
if (TryGetResourceRecursive(Resources, key, out var resource))
|
if (TryGetResourceRecursive(Resources, key, out var resource))
|
||||||
@@ -430,6 +455,11 @@ public sealed partial class OperationCommandBar : CommandBar
|
|||||||
: CommandBarOverflowButtonVisibility.Auto;
|
: CommandBarOverflowButtonVisibility.Auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void InvalidateCommands()
|
||||||
|
{
|
||||||
|
RefreshCommands();
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class SeparatorCommandBarItemViewModel;
|
private sealed class SeparatorCommandBarItemViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<winuiex:WindowEx
|
||||||
|
x:Class="Wino.Mail.WinUI.HostedContentPopoutWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:winuiex="using:WinUIEx"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<Grid
|
||||||
|
x:Name="RootGrid"
|
||||||
|
Padding="4,32,4,4"
|
||||||
|
Background="{ThemeResource WinoApplicationBackgroundColor}">
|
||||||
|
<Grid x:Name="ContentHost">
|
||||||
|
<Grid.ChildrenTransitions>
|
||||||
|
<TransitionCollection>
|
||||||
|
<PopupThemeTransition />
|
||||||
|
</TransitionCollection>
|
||||||
|
</Grid.ChildrenTransitions>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</winuiex:WindowEx>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Wino.Mail.WinUI.Models;
|
||||||
|
using WinUIEx;
|
||||||
|
|
||||||
|
namespace Wino.Mail.WinUI;
|
||||||
|
|
||||||
|
public sealed partial class HostedContentPopoutWindow : WindowEx
|
||||||
|
{
|
||||||
|
private readonly Action _closedCallback;
|
||||||
|
|
||||||
|
public HostedPopoutDescriptor Descriptor { get; }
|
||||||
|
|
||||||
|
public HostedContentPopoutWindow(HostedPopoutDescriptor descriptor, Action closedCallback)
|
||||||
|
{
|
||||||
|
Descriptor = descriptor;
|
||||||
|
_closedCallback = closedCallback;
|
||||||
|
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
Title = descriptor.Title;
|
||||||
|
Width = descriptor.Width;
|
||||||
|
Height = descriptor.Height;
|
||||||
|
MinWidth = descriptor.MinWidth;
|
||||||
|
MinHeight = descriptor.MinHeight;
|
||||||
|
|
||||||
|
ExtendsContentIntoTitleBar = true;
|
||||||
|
|
||||||
|
this.SetIcon("Assets/Wino_Icon.ico");
|
||||||
|
this.CenterOnScreen();
|
||||||
|
|
||||||
|
Closed += OnClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetHostedContent(FrameworkElement content)
|
||||||
|
{
|
||||||
|
ContentHost.Children.Clear();
|
||||||
|
ContentHost.Children.Add(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClosed(object sender, WindowEventArgs args)
|
||||||
|
{
|
||||||
|
Closed -= OnClosed;
|
||||||
|
_closedCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Wino.Mail.WinUI;
|
||||||
|
using Wino.Mail.WinUI.Models;
|
||||||
|
|
||||||
|
namespace Wino.Mail.WinUI.Interfaces;
|
||||||
|
|
||||||
|
public interface IHostedPopoutSource
|
||||||
|
{
|
||||||
|
bool CanPopOutCurrentContent();
|
||||||
|
FrameworkElement? GetCurrentHostedContent();
|
||||||
|
HostedPopoutDescriptor CreatePopoutDescriptor(IPopoutClient client);
|
||||||
|
FrameworkElement DetachHostedContent();
|
||||||
|
void OnHostedContentPoppedOut(FrameworkElement content, HostedContentPopoutWindow window, HostedPopoutDescriptor descriptor);
|
||||||
|
void OnHostedPopoutClosed(FrameworkElement content, HostedPopoutDescriptor descriptor);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using Wino.Mail.WinUI.Models;
|
||||||
|
|
||||||
|
namespace Wino.Mail.WinUI.Interfaces;
|
||||||
|
|
||||||
|
public interface IPopoutClient
|
||||||
|
{
|
||||||
|
bool SupportsPopOut { get; }
|
||||||
|
event EventHandler<PopOutRequestedEventArgs> PopOutRequested;
|
||||||
|
event EventHandler<PopoutHostActionRequestedEventArgs> HostActionRequested;
|
||||||
|
HostedPopoutDescriptor GetPopoutDescriptor();
|
||||||
|
void OnPopoutStateChanged(bool isPoppedOut);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Wino.Mail.WinUI.Models;
|
||||||
|
|
||||||
|
public sealed record HostedPopoutDescriptor(
|
||||||
|
string WindowName,
|
||||||
|
string Title,
|
||||||
|
double Width,
|
||||||
|
double Height,
|
||||||
|
double MinWidth,
|
||||||
|
double MinHeight,
|
||||||
|
string ContentKind);
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Wino.Mail.WinUI.Models;
|
||||||
|
|
||||||
|
public sealed class PopOutRequestedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public static PopOutRequestedEventArgs Default { get; } = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Wino.Mail.WinUI.Models;
|
||||||
|
|
||||||
|
public enum PopoutHostActionKind
|
||||||
|
{
|
||||||
|
CloseHostedInstance,
|
||||||
|
PopOutNextNavigation
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Wino.Mail.WinUI.Models;
|
||||||
|
|
||||||
|
public sealed class PopoutHostActionRequestedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public PopoutHostActionRequestedEventArgs(PopoutHostActionKind actionKind, Type? targetPageType = null, Guid? targetMailUniqueId = null)
|
||||||
|
{
|
||||||
|
ActionKind = actionKind;
|
||||||
|
TargetPageType = targetPageType;
|
||||||
|
TargetMailUniqueId = targetMailUniqueId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PopoutHostActionKind ActionKind { get; }
|
||||||
|
public Type? TargetPageType { get; }
|
||||||
|
public Guid? TargetMailUniqueId { get; }
|
||||||
|
}
|
||||||
@@ -3,5 +3,6 @@ namespace Wino.Mail.WinUI.Models;
|
|||||||
public enum WinoWindowKind
|
public enum WinoWindowKind
|
||||||
{
|
{
|
||||||
Shell,
|
Shell,
|
||||||
Welcome
|
Welcome,
|
||||||
|
HostedPopout
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Mail.WinUI.Interfaces;
|
||||||
|
using Wino.Mail.WinUI.Models;
|
||||||
|
|
||||||
|
namespace Wino.Mail.WinUI.Services;
|
||||||
|
|
||||||
|
public static class HostedContentPopoutCoordinator
|
||||||
|
{
|
||||||
|
public static async Task<bool> PopOutCurrentContentAsync(IHostedPopoutSource source)
|
||||||
|
{
|
||||||
|
if (!source.CanPopOutCurrentContent())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var content = source.GetCurrentHostedContent();
|
||||||
|
if (content is not FrameworkElement frameworkElement || frameworkElement is not IPopoutClient client || !client.SupportsPopOut)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var descriptor = source.CreatePopoutDescriptor(client);
|
||||||
|
var windowManager = WinoApplication.Current.Services.GetRequiredService<IWinoWindowManager>();
|
||||||
|
|
||||||
|
if (windowManager.GetWindow(WinoWindowKind.HostedPopout, descriptor.WindowName) is HostedContentPopoutWindow existingWindow)
|
||||||
|
{
|
||||||
|
windowManager.ActivateWindow(existingWindow);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var detachedContent = source.DetachHostedContent();
|
||||||
|
if (detachedContent is IPopoutClient detachedClient)
|
||||||
|
{
|
||||||
|
detachedClient.OnPopoutStateChanged(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
HostedContentPopoutWindow? popoutWindow = null;
|
||||||
|
|
||||||
|
popoutWindow = (HostedContentPopoutWindow)windowManager.CreateWindow(
|
||||||
|
WinoWindowKind.HostedPopout,
|
||||||
|
() => new HostedContentPopoutWindow(descriptor, () =>
|
||||||
|
{
|
||||||
|
source.OnHostedPopoutClosed(detachedContent, descriptor);
|
||||||
|
}),
|
||||||
|
descriptor.WindowName);
|
||||||
|
|
||||||
|
popoutWindow.SetHostedContent(detachedContent);
|
||||||
|
source.OnHostedContentPoppedOut(detachedContent, popoutWindow, descriptor);
|
||||||
|
windowManager.ActivateWindow(popoutWindow);
|
||||||
|
|
||||||
|
var themeService = WinoApplication.Current.Services.GetService<INewThemeService>();
|
||||||
|
if (themeService != null)
|
||||||
|
{
|
||||||
|
await themeService.ApplyThemeToActiveWindowAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -381,7 +381,10 @@ public class NavigationService : NavigationServiceBase, INavigationService
|
|||||||
&& parameter is MailItemViewModel mailItemViewModel
|
&& parameter is MailItemViewModel mailItemViewModel
|
||||||
&& page != WinoPage.ComposePage)
|
&& page != WinoPage.ComposePage)
|
||||||
{
|
{
|
||||||
WeakReferenceMessenger.Default.Send(new ReaderItemRefreshRequestedEvent(mailItemViewModel));
|
if (listingFrame.Content is MailRenderingPage renderingPage)
|
||||||
|
{
|
||||||
|
_ = renderingPage.RefreshMailItemAsync(mailItemViewModel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (listingFrame.Content != null
|
else if (listingFrame.Content != null
|
||||||
&& listingFrame.Content.GetType() == GetPageType(WinoPage.ComposePage)
|
&& listingFrame.Content.GetType() == GetPageType(WinoPage.ComposePage)
|
||||||
@@ -390,7 +393,10 @@ public class NavigationService : NavigationServiceBase, INavigationService
|
|||||||
{
|
{
|
||||||
// ComposePage is already active and we're switching to another draft.
|
// ComposePage is already active and we're switching to another draft.
|
||||||
// Reuse existing ComposePage and WebView2 instead of navigating.
|
// Reuse existing ComposePage and WebView2 instead of navigating.
|
||||||
WeakReferenceMessenger.Default.Send(new ReaderItemRefreshRequestedEvent(composeDraftViewModel));
|
if (listingFrame.Content is ComposePage composePage)
|
||||||
|
{
|
||||||
|
_ = composePage.RefreshDraftAsync(composeDraftViewModel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (listingFrame.Content != null
|
else if (listingFrame.Content != null
|
||||||
&& listingFrame.Content.GetType() == GetPageType(WinoPage.IdlePage)
|
&& listingFrame.Content.GetType() == GetPageType(WinoPage.IdlePage)
|
||||||
|
|||||||
@@ -172,6 +172,14 @@
|
|||||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||||
</AppBarToggleButton.Icon>
|
</AppBarToggleButton.Icon>
|
||||||
</AppBarToggleButton>
|
</AppBarToggleButton>
|
||||||
|
<AppBarButton
|
||||||
|
Click="PopOutButton_Click"
|
||||||
|
ToolTipService.ToolTip="{x:Bind domain:Translator.Buttons_PopOut}"
|
||||||
|
Visibility="{x:Bind GetPopOutButtonVisibility(), Mode=OneWay}">
|
||||||
|
<AppBarButton.Icon>
|
||||||
|
<coreControls:WinoFontIcon Icon="OpenInNewWindow" />
|
||||||
|
</AppBarButton.Icon>
|
||||||
|
</AppBarButton>
|
||||||
<AppBarButton Click="ToggleEditorThemeClicked" ToolTipService.ToolTip="{x:Bind GetEditorThemeToolTip(WebViewEditor.IsEditorDarkMode), Mode=OneWay}">
|
<AppBarButton Click="ToggleEditorThemeClicked" ToolTipService.ToolTip="{x:Bind GetEditorThemeToolTip(WebViewEditor.IsEditorDarkMode), Mode=OneWay}">
|
||||||
<AppBarButton.Icon>
|
<AppBarButton.Icon>
|
||||||
<coreControls:WinoFontIcon Icon="{x:Bind GetEditorThemeIcon(WebViewEditor.IsEditorDarkMode), Mode=OneWay}" />
|
<coreControls:WinoFontIcon Icon="{x:Bind GetEditorThemeIcon(WebViewEditor.IsEditorDarkMode), Mode=OneWay}" />
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ using Wino.Mail.ViewModels.Data;
|
|||||||
using Wino.Mail.ViewModels.Messages;
|
using Wino.Mail.ViewModels.Messages;
|
||||||
using Wino.Mail.WinUI.Controls;
|
using Wino.Mail.WinUI.Controls;
|
||||||
using Wino.Mail.WinUI.Extensions;
|
using Wino.Mail.WinUI.Extensions;
|
||||||
|
using Wino.Mail.WinUI.Interfaces;
|
||||||
|
using Wino.Mail.WinUI.Models;
|
||||||
using Wino.Messaging.Client.Mails;
|
using Wino.Messaging.Client.Mails;
|
||||||
using Wino.Messaging.Client.Shell;
|
using Wino.Messaging.Client.Shell;
|
||||||
using Wino.Views.Abstract;
|
using Wino.Views.Abstract;
|
||||||
@@ -33,13 +35,19 @@ namespace Wino.Views.Mail;
|
|||||||
|
|
||||||
public sealed partial class ComposePage : ComposePageAbstract,
|
public sealed partial class ComposePage : ComposePageAbstract,
|
||||||
IAiHtmlActionHost,
|
IAiHtmlActionHost,
|
||||||
IRecipient<CreateNewComposeMailRequested>,
|
IPopoutClient,
|
||||||
IRecipient<ApplicationThemeChanged>,
|
IRecipient<ApplicationThemeChanged>
|
||||||
IRecipient<ReaderItemRefreshRequestedEvent>
|
|
||||||
{
|
{
|
||||||
|
private bool _isPoppedOut;
|
||||||
|
|
||||||
|
public bool SupportsPopOut => !_isPoppedOut;
|
||||||
|
public event EventHandler<PopOutRequestedEventArgs>? PopOutRequested;
|
||||||
|
public event EventHandler<PopoutHostActionRequestedEventArgs>? HostActionRequested;
|
||||||
|
|
||||||
public WebView2 GetWebView() => WebViewEditor.GetUnderlyingWebView();
|
public WebView2 GetWebView() => WebViewEditor.GetUnderlyingWebView();
|
||||||
|
|
||||||
public Visibility GetAiActionsToggleVisibility(bool isHidden) => isHidden ? Visibility.Collapsed : Visibility.Visible;
|
public Visibility GetAiActionsToggleVisibility(bool isHidden) => isHidden ? Visibility.Collapsed : Visibility.Visible;
|
||||||
|
public Visibility GetPopOutButtonVisibility() => SupportsPopOut ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
|
||||||
public Visibility GetAiActionsPanelVisibility(bool? isChecked, bool isHidden)
|
public Visibility GetAiActionsPanelVisibility(bool? isChecked, bool isHidden)
|
||||||
=> !isHidden && isChecked == true ? Visibility.Visible : Visibility.Collapsed;
|
=> !isHidden && isChecked == true ? Visibility.Visible : Visibility.Collapsed;
|
||||||
@@ -49,6 +57,28 @@ public sealed partial class ComposePage : ComposePageAbstract,
|
|||||||
public ComposePage()
|
public ComposePage()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
ViewModel.CloseRequested += ViewModel_CloseRequested;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HostedPopoutDescriptor GetPopoutDescriptor()
|
||||||
|
{
|
||||||
|
var title = string.IsNullOrWhiteSpace(ViewModel.Subject) ? Translator.Draft : ViewModel.Subject;
|
||||||
|
var draftId = ViewModel.CurrentMailDraftItem?.MailCopy?.UniqueId.ToString("N") ?? title;
|
||||||
|
|
||||||
|
return new HostedPopoutDescriptor(
|
||||||
|
$"compose-{draftId}",
|
||||||
|
title,
|
||||||
|
1180,
|
||||||
|
860,
|
||||||
|
760,
|
||||||
|
600,
|
||||||
|
nameof(ComposePage));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPopoutStateChanged(bool isPoppedOut)
|
||||||
|
{
|
||||||
|
_isPoppedOut = isPoppedOut;
|
||||||
|
Bindings.Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
public WinoIconGlyph GetEditorThemeIcon(bool isDarkMode) => isDarkMode ? WinoIconGlyph.LightEditor : WinoIconGlyph.DarkEditor;
|
public WinoIconGlyph GetEditorThemeIcon(bool isDarkMode) => isDarkMode ? WinoIconGlyph.LightEditor : WinoIconGlyph.DarkEditor;
|
||||||
@@ -277,11 +307,7 @@ public sealed partial class ComposePage : ComposePageAbstract,
|
|||||||
_disposables.Add(WebViewEditor);
|
_disposables.Add(WebViewEditor);
|
||||||
|
|
||||||
ViewModel.GetHTMLBodyFunction = WebViewEditor.GetHtmlBodyAsync;
|
ViewModel.GetHTMLBodyFunction = WebViewEditor.GetHtmlBodyAsync;
|
||||||
}
|
ViewModel.RenderHtmlBodyAsyncFunc = WebViewEditor.RenderHtmlAsync;
|
||||||
|
|
||||||
async void IRecipient<CreateNewComposeMailRequested>.Receive(CreateNewComposeMailRequested message)
|
|
||||||
{
|
|
||||||
await WebViewEditor.RenderHtmlAsync(message.RenderModel.RenderHtml);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowCCBCCClicked(object sender, RoutedEventArgs e)
|
private void ShowCCBCCClicked(object sender, RoutedEventArgs e)
|
||||||
@@ -289,6 +315,16 @@ public sealed partial class ComposePage : ComposePageAbstract,
|
|||||||
ViewModel.IsCCBCCVisible = true;
|
ViewModel.IsCCBCCVisible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void PopOutButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
PopOutRequested?.Invoke(this, PopOutRequestedEventArgs.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ViewModel_CloseRequested(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
HostActionRequested?.Invoke(this, new PopoutHostActionRequestedEventArgs(PopoutHostActionKind.CloseHostedInstance));
|
||||||
|
}
|
||||||
|
|
||||||
private async void ComposeAiActionsToggleButton_Checked(object sender, RoutedEventArgs e)
|
private async void ComposeAiActionsToggleButton_Checked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
await ComposeAiActionsPanel.RefreshAvailabilityAsync();
|
await ComposeAiActionsPanel.RefreshAvailabilityAsync();
|
||||||
@@ -333,12 +369,13 @@ public sealed partial class ComposePage : ComposePageAbstract,
|
|||||||
WebViewEditor.IsEditorDarkMode = message.IsUnderlyingThemeDark;
|
WebViewEditor.IsEditorDarkMode = message.IsUnderlyingThemeDark;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IRecipient<ReaderItemRefreshRequestedEvent>.Receive(ReaderItemRefreshRequestedEvent message)
|
public async Task RefreshDraftAsync(MailItemViewModel draftMailItemViewModel)
|
||||||
{
|
{
|
||||||
if (message.MailItemViewModel == null || !message.MailItemViewModel.IsDraft) return;
|
if (draftMailItemViewModel == null || !draftMailItemViewModel.IsDraft) return;
|
||||||
|
|
||||||
// Reset the initial focus flag so ToBox gets focus for the new draft.
|
// Reset the initial focus flag so ToBox gets focus for the new draft.
|
||||||
isInitialFocusHandled = false;
|
isInitialFocusHandled = false;
|
||||||
|
await ViewModel.RefreshDraftAsync(draftMailItemViewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ImportanceClicked(object sender, RoutedEventArgs e)
|
private void ImportanceClicked(object sender, RoutedEventArgs e)
|
||||||
@@ -423,6 +460,7 @@ public sealed partial class ComposePage : ComposePageAbstract,
|
|||||||
FocusManager.GotFocus -= GlobalFocusManagerGotFocus;
|
FocusManager.GotFocus -= GlobalFocusManagerGotFocus;
|
||||||
ComposeAiActionsPanel.CancelPendingOperation();
|
ComposeAiActionsPanel.CancelPendingOperation();
|
||||||
await ViewModel.UpdateMimeChangesAsync();
|
await ViewModel.UpdateMimeChangesAsync();
|
||||||
|
ViewModel.RenderHtmlBodyAsyncFunc = null;
|
||||||
|
|
||||||
DisposeDisposables();
|
DisposeDisposables();
|
||||||
}
|
}
|
||||||
@@ -496,18 +534,14 @@ public sealed partial class ComposePage : ComposePageAbstract,
|
|||||||
{
|
{
|
||||||
base.RegisterRecipients();
|
base.RegisterRecipients();
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Register<CreateNewComposeMailRequested>(this);
|
|
||||||
WeakReferenceMessenger.Default.Register<ApplicationThemeChanged>(this);
|
WeakReferenceMessenger.Default.Register<ApplicationThemeChanged>(this);
|
||||||
WeakReferenceMessenger.Default.Register<ReaderItemRefreshRequestedEvent>(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UnregisterRecipients()
|
protected override void UnregisterRecipients()
|
||||||
{
|
{
|
||||||
base.UnregisterRecipients();
|
base.UnregisterRecipients();
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Unregister<CreateNewComposeMailRequested>(this);
|
|
||||||
WeakReferenceMessenger.Default.Unregister<ApplicationThemeChanged>(this);
|
WeakReferenceMessenger.Default.Unregister<ApplicationThemeChanged>(this);
|
||||||
WeakReferenceMessenger.Default.Unregister<ReaderItemRefreshRequestedEvent>(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Save mime on closing the app.
|
// TODO: Save mime on closing the app.
|
||||||
|
|||||||
@@ -176,12 +176,11 @@
|
|||||||
<coreControls:OperationCommandBar
|
<coreControls:OperationCommandBar
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
DefaultLabelPosition="Collapsed"
|
DefaultLabelPosition="Collapsed"
|
||||||
IsEnabled="{x:Bind helpers:XamlHelpers.CountToBooleanConverter(ViewModel.MailCollection.SelectedItemsCount), Mode=OneWay}"
|
|
||||||
IsAIActionsPaneToggleVisible="False"
|
IsAIActionsPaneToggleVisible="False"
|
||||||
|
IsEnabled="{x:Bind helpers:XamlHelpers.CountToBooleanConverter(ViewModel.MailCollection.SelectedItemsCount), Mode=OneWay}"
|
||||||
ItemInvokedCommand="{x:Bind ViewModel.ExecuteTopBarActionCommand}"
|
ItemInvokedCommand="{x:Bind ViewModel.ExecuteTopBarActionCommand}"
|
||||||
MenuItems="{x:Bind ViewModel.ActionItems, Mode=OneWay}"
|
MenuItems="{x:Bind ViewModel.ActionItems, Mode=OneWay}"
|
||||||
OverflowButtonVisibility="Auto">
|
OverflowButtonVisibility="Auto" />
|
||||||
</coreControls:OperationCommandBar>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Pivot + Sync + Multi Select -->
|
<!-- Pivot + Sync + Multi Select -->
|
||||||
@@ -359,7 +358,7 @@
|
|||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
HorizontalContentAlignment="Stretch"
|
HorizontalContentAlignment="Stretch"
|
||||||
toolkitExt:ListViewExtensions.ItemContainerStretchDirection="Horizontal"
|
toolkitExt:ListViewExtensions.ItemContainerStretchDirection="Horizontal"
|
||||||
toolkitExt:ScrollViewerExtensions.VerticalScrollBarMargin="0"
|
toolkitExt:ScrollViewerExtensions.VerticalScrollBarMargin="-6"
|
||||||
CanDragItems="True"
|
CanDragItems="True"
|
||||||
ChoosingItemContainer="WinoListViewChoosingItemContainer"
|
ChoosingItemContainer="WinoListViewChoosingItemContainer"
|
||||||
IsItemClickEnabled="True"
|
IsItemClickEnabled="True"
|
||||||
@@ -377,7 +376,7 @@
|
|||||||
</listview:WinoListView.ItemContainerTransitions>
|
</listview:WinoListView.ItemContainerTransitions>
|
||||||
<listview:WinoListView.ItemsPanel>
|
<listview:WinoListView.ItemsPanel>
|
||||||
<ItemsPanelTemplate>
|
<ItemsPanelTemplate>
|
||||||
<ItemsStackPanel AreStickyGroupHeadersEnabled="True" GroupPadding="10,10,10,0" />
|
<ItemsStackPanel AreStickyGroupHeadersEnabled="True" GroupPadding="4,0,4,4" />
|
||||||
</ItemsPanelTemplate>
|
</ItemsPanelTemplate>
|
||||||
</listview:WinoListView.ItemsPanel>
|
</listview:WinoListView.ItemsPanel>
|
||||||
<listview:WinoListView.Resources>
|
<listview:WinoListView.Resources>
|
||||||
@@ -488,7 +487,7 @@
|
|||||||
<Grid
|
<Grid
|
||||||
x:Name="RenderingGrid"
|
x:Name="RenderingGrid"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="7,0,0,0">
|
Margin="0,3,0,0">
|
||||||
<!-- Mail Rendering Frame -->
|
<!-- Mail Rendering Frame -->
|
||||||
<Frame x:Name="RenderingFrame" IsNavigationStackEnabled="False" />
|
<Frame x:Name="RenderingFrame" IsNavigationStackEnabled="False" />
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,10 @@ using Wino.Mail.ViewModels.Messages;
|
|||||||
using Wino.Mail.WinUI;
|
using Wino.Mail.WinUI;
|
||||||
using Wino.Mail.WinUI.Controls.ListView;
|
using Wino.Mail.WinUI.Controls.ListView;
|
||||||
using Wino.Mail.WinUI.Extensions;
|
using Wino.Mail.WinUI.Extensions;
|
||||||
|
using Wino.Mail.WinUI.Helpers;
|
||||||
using Wino.Mail.WinUI.Interfaces;
|
using Wino.Mail.WinUI.Interfaces;
|
||||||
using Wino.Mail.WinUI.Models;
|
using Wino.Mail.WinUI.Models;
|
||||||
|
using Wino.Mail.WinUI.Services;
|
||||||
using Wino.MenuFlyouts.Context;
|
using Wino.MenuFlyouts.Context;
|
||||||
using Wino.Messaging.Client.Mails;
|
using Wino.Messaging.Client.Mails;
|
||||||
using Wino.Views.Abstract;
|
using Wino.Views.Abstract;
|
||||||
@@ -48,12 +50,16 @@ public sealed partial class MailListPage : MailListPageAbstract,
|
|||||||
IRecipient<ActiveMailItemChangedEvent>,
|
IRecipient<ActiveMailItemChangedEvent>,
|
||||||
IRecipient<SelectMailItemContainerEvent>,
|
IRecipient<SelectMailItemContainerEvent>,
|
||||||
IRecipient<DisposeRenderingFrameRequested>,
|
IRecipient<DisposeRenderingFrameRequested>,
|
||||||
|
IHostedPopoutSource,
|
||||||
ITitleBarSearchHost
|
ITitleBarSearchHost
|
||||||
{
|
{
|
||||||
private const double RENDERING_COLUMN_MIN_WIDTH = 375;
|
private const double RENDERING_COLUMN_MIN_WIDTH = 375;
|
||||||
private const int SELECTION_SETTLE_DELAY_MS = 120;
|
private const int SELECTION_SETTLE_DELAY_MS = 120;
|
||||||
private const int RENDERING_FRAME_RELEASE_DELAY_MS = 2000;
|
private const int RENDERING_FRAME_RELEASE_DELAY_MS = 2000;
|
||||||
private int _idleNavigationRequestVersion = 0;
|
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 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)}");
|
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()
|
public MailListPage()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
RenderingFrame.Navigated += RenderingFrame_Navigated;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||||
@@ -101,6 +108,7 @@ public sealed partial class MailListPage : MailListPageAbstract,
|
|||||||
base.OnNavigatedFrom(e);
|
base.OnNavigatedFrom(e);
|
||||||
|
|
||||||
InvalidatePendingIdleNavigation();
|
InvalidatePendingIdleNavigation();
|
||||||
|
DetachPopoutClient();
|
||||||
|
|
||||||
this.Bindings.StopTracking();
|
this.Bindings.StopTracking();
|
||||||
|
|
||||||
@@ -325,7 +333,10 @@ public sealed partial class MailListPage : MailListPageAbstract,
|
|||||||
PrepareRenderingPageWebViewTransition();
|
PrepareRenderingPageWebViewTransition();
|
||||||
|
|
||||||
// Dispose existing HTML content from rendering page webview.
|
// Dispose existing HTML content from rendering page webview.
|
||||||
WeakReferenceMessenger.Default.Send(new CancelRenderingContentRequested());
|
if (RenderingFrame.Content is MailRenderingPage renderingPage)
|
||||||
|
{
|
||||||
|
_ = renderingPage.ClearRenderedContentAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (IsComposingPageActive())
|
else if (IsComposingPageActive())
|
||||||
{
|
{
|
||||||
@@ -359,6 +370,55 @@ public sealed partial class MailListPage : MailListPageAbstract,
|
|||||||
private bool IsRenderingPageActive() => RenderingFrame.Content is MailRenderingPage;
|
private bool IsRenderingPageActive() => RenderingFrame.Content is MailRenderingPage;
|
||||||
private bool IsComposingPageActive() => RenderingFrame.Content is ComposePage;
|
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()
|
private void InvalidatePendingIdleNavigation()
|
||||||
{
|
{
|
||||||
unchecked
|
unchecked
|
||||||
@@ -378,7 +438,10 @@ public sealed partial class MailListPage : MailListPageAbstract,
|
|||||||
|
|
||||||
if (IsRenderingPageActive())
|
if (IsRenderingPageActive())
|
||||||
{
|
{
|
||||||
WeakReferenceMessenger.Default.Send(new CancelRenderingContentRequested());
|
if (RenderingFrame.Content is MailRenderingPage renderingPage)
|
||||||
|
{
|
||||||
|
_ = renderingPage.ClearRenderedContentAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(RENDERING_FRAME_RELEASE_DELAY_MS);
|
await Task.Delay(RENDERING_FRAME_RELEASE_DELAY_MS);
|
||||||
@@ -926,4 +989,125 @@ public sealed partial class MailListPage : MailListPageAbstract,
|
|||||||
|
|
||||||
return Task.CompletedTask;
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,8 +272,10 @@
|
|||||||
IsAIActionsPaneToggleVisible="{x:Bind GetAiActionsToggleVisible(ViewModel.PreferencesService.IsAiActionsPanelHidden), Mode=OneWay}"
|
IsAIActionsPaneToggleVisible="{x:Bind GetAiActionsToggleVisible(ViewModel.PreferencesService.IsAiActionsPanelHidden), Mode=OneWay}"
|
||||||
IsEditorThemeDark="{x:Bind ViewModel.IsDarkWebviewRenderer, Mode=TwoWay}"
|
IsEditorThemeDark="{x:Bind ViewModel.IsDarkWebviewRenderer, Mode=TwoWay}"
|
||||||
IsEditorThemeToggleVisible="True"
|
IsEditorThemeToggleVisible="True"
|
||||||
|
IsPopOutButtonVisible="{x:Bind SupportsPopOut, Mode=OneWay}"
|
||||||
ItemInvokedCommand="{x:Bind ViewModel.OperationClickedCommand}"
|
ItemInvokedCommand="{x:Bind ViewModel.OperationClickedCommand}"
|
||||||
MenuItems="{x:Bind ViewModel.MenuItems, Mode=OneWay}">
|
MenuItems="{x:Bind ViewModel.MenuItems, Mode=OneWay}"
|
||||||
|
PopOutClicked="RendererCommandBar_PopOutClicked">
|
||||||
<coreControls:OperationCommandBar.Content>
|
<coreControls:OperationCommandBar.Content>
|
||||||
<Grid Padding="0,5">
|
<Grid Padding="0,5">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
|
|||||||
@@ -16,9 +16,12 @@ using Wino.Core.Domain.Enums;
|
|||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.Printing;
|
using Wino.Core.Domain.Models.Printing;
|
||||||
using Wino.Mail.ViewModels.Data;
|
using Wino.Mail.ViewModels.Data;
|
||||||
|
using Wino.Mail.ViewModels.Models;
|
||||||
using Wino.Mail.WinUI;
|
using Wino.Mail.WinUI;
|
||||||
using Wino.Mail.WinUI.Controls;
|
using Wino.Mail.WinUI.Controls;
|
||||||
using Wino.Mail.WinUI.Extensions;
|
using Wino.Mail.WinUI.Extensions;
|
||||||
|
using Wino.Mail.WinUI.Interfaces;
|
||||||
|
using Wino.Mail.WinUI.Models;
|
||||||
using Wino.Messaging.Client.Mails;
|
using Wino.Messaging.Client.Mails;
|
||||||
using Wino.Messaging.Client.Shell;
|
using Wino.Messaging.Client.Shell;
|
||||||
using Wino.Views.Abstract;
|
using Wino.Views.Abstract;
|
||||||
@@ -27,8 +30,7 @@ namespace Wino.Views.Mail;
|
|||||||
|
|
||||||
public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
||||||
IAiHtmlActionHost,
|
IAiHtmlActionHost,
|
||||||
IRecipient<HtmlRenderingRequested>,
|
IPopoutClient,
|
||||||
IRecipient<CancelRenderingContentRequested>,
|
|
||||||
IRecipient<ApplicationThemeChanged>
|
IRecipient<ApplicationThemeChanged>
|
||||||
{
|
{
|
||||||
private readonly IPreferencesService _preferencesService = App.Current.Services.GetService<IPreferencesService>()!;
|
private readonly IPreferencesService _preferencesService = App.Current.Services.GetService<IPreferencesService>()!;
|
||||||
@@ -39,6 +41,11 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
|||||||
private bool? _lastAppliedDarkTheme;
|
private bool? _lastAppliedDarkTheme;
|
||||||
private TaskCompletionSource<bool> DOMLoadedTask = new TaskCompletionSource<bool>();
|
private TaskCompletionSource<bool> DOMLoadedTask = new TaskCompletionSource<bool>();
|
||||||
private string _currentRenderedHtml = string.Empty;
|
private string _currentRenderedHtml = string.Empty;
|
||||||
|
private bool _isPoppedOut;
|
||||||
|
|
||||||
|
public bool SupportsPopOut => !_isPoppedOut;
|
||||||
|
public event EventHandler<PopOutRequestedEventArgs>? PopOutRequested;
|
||||||
|
public event EventHandler<PopoutHostActionRequestedEventArgs>? HostActionRequested;
|
||||||
|
|
||||||
public WebView2 GetWebView() => Chromium;
|
public WebView2 GetWebView() => Chromium;
|
||||||
public bool GetAiActionsToggleVisible(bool isHidden) => !isHidden;
|
public bool GetAiActionsToggleVisible(bool isHidden) => !isHidden;
|
||||||
@@ -57,9 +64,34 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
|||||||
{
|
{
|
||||||
return Chromium.CoreWebView2.PrintToPdfAsync(path, null).AsTask();
|
return Chromium.CoreWebView2.PrintToPdfAsync(path, null).AsTask();
|
||||||
});
|
});
|
||||||
|
ViewModel.RenderHtmlAsyncFunc = RenderInternalAsync;
|
||||||
|
ViewModel.ClearRenderedHtmlAsyncFunc = ClearRenderedContentAsync;
|
||||||
|
ViewModel.CloseRequested += ViewModel_CloseRequested;
|
||||||
|
ViewModel.ComposeRequested += ViewModel_ComposeRequested;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HostedPopoutDescriptor GetPopoutDescriptor()
|
||||||
|
{
|
||||||
|
var title = string.IsNullOrWhiteSpace(ViewModel.Subject) ? Translator.MailItemNoSubject : ViewModel.Subject;
|
||||||
|
var uniquePart = ViewModel.CurrentMailFileId?.ToString("N") ?? title;
|
||||||
|
return new HostedPopoutDescriptor(
|
||||||
|
$"mail-rendering-{uniquePart}",
|
||||||
|
title,
|
||||||
|
1080,
|
||||||
|
780,
|
||||||
|
640,
|
||||||
|
480,
|
||||||
|
nameof(MailRenderingPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPopoutStateChanged(bool isPoppedOut)
|
||||||
|
{
|
||||||
|
_isPoppedOut = isPoppedOut;
|
||||||
|
Bindings.Update();
|
||||||
|
RendererCommandBar.InvalidateCommands();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<PrintingResult> DirectPrintAsync(WebView2PrintSettingsModel settings)
|
private async Task<PrintingResult> DirectPrintAsync(WebView2PrintSettingsModel settings)
|
||||||
{
|
{
|
||||||
if (Chromium.CoreWebView2 == null) return PrintingResult.Failed;
|
if (Chromium.CoreWebView2 == null) return PrintingResult.Failed;
|
||||||
@@ -132,20 +164,14 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
|||||||
|
|
||||||
private void DOMContentLoaded(CoreWebView2 sender, CoreWebView2DOMContentLoadedEventArgs args) => DOMLoadedTask.TrySetResult(true);
|
private void DOMContentLoaded(CoreWebView2 sender, CoreWebView2DOMContentLoadedEventArgs args) => DOMLoadedTask.TrySetResult(true);
|
||||||
|
|
||||||
async void IRecipient<HtmlRenderingRequested>.Receive(HtmlRenderingRequested message)
|
public async Task ClearRenderedContentAsync()
|
||||||
{
|
{
|
||||||
// Ensure WebView2 is fully initialized before first render.
|
|
||||||
// OnNavigatedTo starts initialization fire-and-forget; this await
|
|
||||||
// guarantees the core is ready before we invoke any script.
|
|
||||||
await EnsureChromiumInitializedAsync();
|
await EnsureChromiumInitializedAsync();
|
||||||
|
|
||||||
if (message == null || string.IsNullOrEmpty(message.HtmlBody))
|
if (!isRenderingInProgress)
|
||||||
{
|
{
|
||||||
await RenderInternalAsync(string.Empty);
|
await RenderInternalAsync(string.Empty);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await RenderInternalAsync(message.HtmlBody);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
||||||
@@ -157,8 +183,11 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
|||||||
|
|
||||||
ViewModel.SaveHTMLasPDFFunc = null;
|
ViewModel.SaveHTMLasPDFFunc = null;
|
||||||
ViewModel.DirectPrintFuncAsync = null;
|
ViewModel.DirectPrintFuncAsync = null;
|
||||||
|
ViewModel.RenderHtmlAsyncFunc = null;
|
||||||
|
ViewModel.ClearRenderedHtmlAsyncFunc = null;
|
||||||
_currentRenderedHtml = string.Empty;
|
_currentRenderedHtml = string.Empty;
|
||||||
RendererCommandBar.AIActionsEnabledChanged -= RendererCommandBar_AIActionsEnabledChanged;
|
RendererCommandBar.AIActionsEnabledChanged -= RendererCommandBar_AIActionsEnabledChanged;
|
||||||
|
RendererCommandBar.PopOutClicked -= RendererCommandBar_PopOutClicked;
|
||||||
RendererCommandBar.IsAIActionsEnabled = false;
|
RendererCommandBar.IsAIActionsEnabled = false;
|
||||||
ReaderAiActionsPanel.CancelPendingOperation();
|
ReaderAiActionsPanel.CancelPendingOperation();
|
||||||
|
|
||||||
@@ -178,6 +207,11 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
|||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task RefreshMailItemAsync(MailItemViewModel mailItemViewModel)
|
||||||
|
{
|
||||||
|
return ViewModel.RefreshMailItemAsync(mailItemViewModel);
|
||||||
|
}
|
||||||
|
|
||||||
private async void RendererCommandBar_AIActionsEnabledChanged(object? sender, bool isEnabled)
|
private async void RendererCommandBar_AIActionsEnabledChanged(object? sender, bool isEnabled)
|
||||||
{
|
{
|
||||||
if (isEnabled)
|
if (isEnabled)
|
||||||
@@ -275,11 +309,13 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
|||||||
|
|
||||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||||
{
|
{
|
||||||
// Initialize WebView2 wiring before base navigation invokes ViewModel rendering.
|
|
||||||
// Base.OnNavigatedTo triggers VM.OnNavigatedTo, which can send HtmlRenderingRequested.
|
|
||||||
DOMLoadedTask = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
DOMLoadedTask = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
ViewModel.RenderHtmlAsyncFunc = RenderInternalAsync;
|
||||||
|
ViewModel.ClearRenderedHtmlAsyncFunc = ClearRenderedContentAsync;
|
||||||
RendererCommandBar.AIActionsEnabledChanged -= RendererCommandBar_AIActionsEnabledChanged;
|
RendererCommandBar.AIActionsEnabledChanged -= RendererCommandBar_AIActionsEnabledChanged;
|
||||||
RendererCommandBar.AIActionsEnabledChanged += RendererCommandBar_AIActionsEnabledChanged;
|
RendererCommandBar.AIActionsEnabledChanged += RendererCommandBar_AIActionsEnabledChanged;
|
||||||
|
RendererCommandBar.PopOutClicked -= RendererCommandBar_PopOutClicked;
|
||||||
|
RendererCommandBar.PopOutClicked += RendererCommandBar_PopOutClicked;
|
||||||
RendererCommandBar.IsAIActionsEnabled = false;
|
RendererCommandBar.IsAIActionsEnabled = false;
|
||||||
Chromium.CoreWebView2Initialized -= CoreWebViewInitialized;
|
Chromium.CoreWebView2Initialized -= CoreWebViewInitialized;
|
||||||
Chromium.CoreWebView2Initialized += CoreWebViewInitialized;
|
Chromium.CoreWebView2Initialized += CoreWebViewInitialized;
|
||||||
@@ -316,17 +352,6 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
|||||||
Chromium.Source = new Uri("https://wino.mail/reader.html");
|
Chromium.Source = new Uri("https://wino.mail/reader.html");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async void IRecipient<CancelRenderingContentRequested>.Receive(CancelRenderingContentRequested message)
|
|
||||||
{
|
|
||||||
await EnsureChromiumInitializedAsync();
|
|
||||||
|
|
||||||
if (!isRenderingInProgress)
|
|
||||||
{
|
|
||||||
await RenderInternalAsync(string.Empty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void WebViewNavigationStarting(WebView2 sender, CoreWebView2NavigationStartingEventArgs args)
|
private async void WebViewNavigationStarting(WebView2 sender, CoreWebView2NavigationStartingEventArgs args)
|
||||||
{
|
{
|
||||||
// This is our reader.
|
// This is our reader.
|
||||||
@@ -397,7 +422,8 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
|||||||
|
|
||||||
private void InternetAddressClicked(object sender, RoutedEventArgs e)
|
private void InternetAddressClicked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is HyperlinkButton hyperlinkButton)
|
// TODO: Popped out windows don't have xaml root assigned properly, therefore ShowAt will fail.
|
||||||
|
if (sender is HyperlinkButton hyperlinkButton && !_isPoppedOut)
|
||||||
{
|
{
|
||||||
hyperlinkButton.ContextFlyout.ShowAt(hyperlinkButton);
|
hyperlinkButton.ContextFlyout.ShowAt(hyperlinkButton);
|
||||||
}
|
}
|
||||||
@@ -411,6 +437,21 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RendererCommandBar_PopOutClicked(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
PopOutRequested?.Invoke(this, PopOutRequestedEventArgs.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ViewModel_CloseRequested(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
HostActionRequested?.Invoke(this, new PopoutHostActionRequestedEventArgs(PopoutHostActionKind.CloseHostedInstance));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ViewModel_ComposeRequested(object? sender, ComposeDraftRequestedEventArgs e)
|
||||||
|
{
|
||||||
|
HostActionRequested?.Invoke(this, new PopoutHostActionRequestedEventArgs(PopoutHostActionKind.PopOutNextNavigation, typeof(ComposePage), e.DraftUniqueId));
|
||||||
|
}
|
||||||
|
|
||||||
private void OpenAttachment_Click(object sender, RoutedEventArgs e)
|
private void OpenAttachment_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is MenuFlyoutItem item && item.CommandParameter is MailAttachmentViewModel attachment)
|
if (sender is MenuFlyoutItem item && item.CommandParameter is MailAttachmentViewModel attachment)
|
||||||
@@ -431,8 +472,6 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
|||||||
{
|
{
|
||||||
base.RegisterRecipients();
|
base.RegisterRecipients();
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Register<HtmlRenderingRequested>(this);
|
|
||||||
WeakReferenceMessenger.Default.Register<CancelRenderingContentRequested>(this);
|
|
||||||
WeakReferenceMessenger.Default.Register<ApplicationThemeChanged>(this);
|
WeakReferenceMessenger.Default.Register<ApplicationThemeChanged>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,8 +479,6 @@ public sealed partial class MailRenderingPage : MailRenderingPageAbstract,
|
|||||||
{
|
{
|
||||||
base.UnregisterRecipients();
|
base.UnregisterRecipients();
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Unregister<HtmlRenderingRequested>(this);
|
|
||||||
WeakReferenceMessenger.Default.Unregister<CancelRenderingContentRequested>(this);
|
|
||||||
WeakReferenceMessenger.Default.Unregister<ApplicationThemeChanged>(this);
|
WeakReferenceMessenger.Default.Unregister<ApplicationThemeChanged>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user