Simplified compoper and rendering logic through messages.
This commit is contained in:
@@ -29,7 +29,7 @@ using Wino.Messaging.UI;
|
|||||||
namespace Wino.Mail.ViewModels;
|
namespace Wino.Mail.ViewModels;
|
||||||
|
|
||||||
public partial class ComposePageViewModel : MailBaseViewModel,
|
public partial class ComposePageViewModel : MailBaseViewModel,
|
||||||
IRecipient<NewComposeDraftItemRequestedEvent>,
|
IRecipient<ReaderItemRefreshRequestedEvent>,
|
||||||
IRecipient<SynchronizationActionsAdded>,
|
IRecipient<SynchronizationActionsAdded>,
|
||||||
IRecipient<SynchronizationActionsCompleted>,
|
IRecipient<SynchronizationActionsCompleted>,
|
||||||
IRecipient<AccountSynchronizerStateChanged>
|
IRecipient<AccountSynchronizerStateChanged>
|
||||||
@@ -511,8 +511,10 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void Receive(NewComposeDraftItemRequestedEvent message)
|
public async void Receive(ReaderItemRefreshRequestedEvent message)
|
||||||
{
|
{
|
||||||
|
if (message.MailItemViewModel == null || !message.MailItemViewModel.IsDraft) return;
|
||||||
|
|
||||||
// Save current draft before switching.
|
// Save current draft before switching.
|
||||||
await UpdateMimeChangesAsync();
|
await UpdateMimeChangesAsync();
|
||||||
|
|
||||||
@@ -555,7 +557,7 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
{
|
{
|
||||||
base.RegisterRecipients();
|
base.RegisterRecipients();
|
||||||
|
|
||||||
Messenger.Register<NewComposeDraftItemRequestedEvent>(this);
|
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);
|
||||||
@@ -565,7 +567,7 @@ public partial class ComposePageViewModel : MailBaseViewModel,
|
|||||||
{
|
{
|
||||||
base.UnregisterRecipients();
|
base.UnregisterRecipients();
|
||||||
|
|
||||||
Messenger.Unregister<NewComposeDraftItemRequestedEvent>(this);
|
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);
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
namespace Wino.Mail.ViewModels.Data;
|
namespace Wino.Mail.ViewModels.Data;
|
||||||
|
|
||||||
public partial class AccountContactViewModel : ObservableObject
|
public partial class AccountContactViewModel : ObservableObject, IMailItemDisplayInformation
|
||||||
{
|
{
|
||||||
public string Address { get; set; }
|
public string Address { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
@@ -49,4 +50,25 @@ public partial class AccountContactViewModel : ObservableObject
|
|||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial bool ThumbnailUpdatedEvent { get; set; }
|
public partial bool ThumbnailUpdatedEvent { get; set; }
|
||||||
|
|
||||||
|
// IMailItemDisplayInformation implementation for avatar-only rendering.
|
||||||
|
public string Subject => string.Empty;
|
||||||
|
public string FromName => Name ?? string.Empty;
|
||||||
|
public string FromAddress => Address ?? string.Empty;
|
||||||
|
public string PreviewText => string.Empty;
|
||||||
|
public bool IsRead => true;
|
||||||
|
public bool IsDraft => false;
|
||||||
|
public bool HasAttachments => false;
|
||||||
|
public bool IsCalendarEvent => false;
|
||||||
|
public bool IsFlagged => false;
|
||||||
|
public DateTime CreationDate => default;
|
||||||
|
public bool IsBusy => false;
|
||||||
|
public bool IsThreadExpanded => false;
|
||||||
|
public AccountContact SenderContact => new()
|
||||||
|
{
|
||||||
|
Address = Address,
|
||||||
|
Name = Name,
|
||||||
|
Base64ContactPicture = Base64ContactPicture,
|
||||||
|
IsRootContact = IsRootContact
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,9 +226,17 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
|||||||
|
|
||||||
if (mode == NavigationMode.Back)
|
if (mode == NavigationMode.Back)
|
||||||
{
|
{
|
||||||
// Account list may have changed while this shell was inactive.
|
// Preserve current mail/folder selection and active rendering page when
|
||||||
|
// switching back from Calendar mode. Recreating menu/folder state here
|
||||||
|
// causes a folder reload, which clears selection and disposes reader page.
|
||||||
|
// Rehydrate only if menu state is unexpectedly empty.
|
||||||
|
if (MenuItems?.Any() != true || FooterItems?.Any() != true)
|
||||||
|
{
|
||||||
|
await CreateFooterItemsAsync();
|
||||||
await RecreateMenuItemsAsync();
|
await RecreateMenuItemsAsync();
|
||||||
await RestoreSelectedAccountAfterMenuRefreshAsync(false);
|
await RestoreSelectedAccountAfterMenuRefreshAsync(false);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ 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<NewMailItemRenderingRequestedEvent>,
|
IRecipient<ReaderItemRefreshRequestedEvent>,
|
||||||
IRecipient<ThumbnailAdded>,
|
IRecipient<ThumbnailAdded>,
|
||||||
ITransferProgress // For listening IMAP message download progress.
|
ITransferProgress // For listening IMAP message download progress.
|
||||||
{
|
{
|
||||||
@@ -127,6 +127,9 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial string ContactPicture { get; set; }
|
public partial string ContactPicture { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial IMailItemDisplayInformation CurrentMailItemDisplayInformation { get; set; }
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial DateTime CreationDate { get; set; }
|
public partial DateTime CreationDate { get; set; }
|
||||||
public ObservableCollection<AccountContactViewModel> ToItems { get; set; } = [];
|
public ObservableCollection<AccountContactViewModel> ToItems { get; set; } = [];
|
||||||
@@ -361,6 +364,7 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
|
|
||||||
initializedMailItemViewModel = null;
|
initializedMailItemViewModel = null;
|
||||||
initializedMimeMessageInformation = null;
|
initializedMimeMessageInformation = null;
|
||||||
|
CurrentMailItemDisplayInformation = null;
|
||||||
|
|
||||||
// Dispose existing content first.
|
// Dispose existing content first.
|
||||||
Messenger.Send(new CancelRenderingContentRequested());
|
Messenger.Send(new CancelRenderingContentRequested());
|
||||||
@@ -447,6 +451,7 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
}
|
}
|
||||||
|
|
||||||
initializedMailItemViewModel = mailItemViewModel;
|
initializedMailItemViewModel = mailItemViewModel;
|
||||||
|
await ExecuteUIThread(() => { CurrentMailItemDisplayInformation = mailItemViewModel; });
|
||||||
await RenderAsync(mimeMessageInformation);
|
await RenderAsync(mimeMessageInformation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,6 +575,7 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
|
|
||||||
initializedMailItemViewModel = null;
|
initializedMailItemViewModel = null;
|
||||||
initializedMimeMessageInformation = null;
|
initializedMimeMessageInformation = null;
|
||||||
|
CurrentMailItemDisplayInformation = null;
|
||||||
|
|
||||||
forceImageLoading = false;
|
forceImageLoading = false;
|
||||||
|
|
||||||
@@ -798,8 +804,10 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
// For upload.
|
// For upload.
|
||||||
void ITransferProgress.Report(long bytesTransferred) { }
|
void ITransferProgress.Report(long bytesTransferred) { }
|
||||||
|
|
||||||
public async void Receive(NewMailItemRenderingRequestedEvent message)
|
public async void Receive(ReaderItemRefreshRequestedEvent message)
|
||||||
{
|
{
|
||||||
|
if (message.MailItemViewModel == null || message.MailItemViewModel.IsDraft) return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await RenderAsync(message.MailItemViewModel, renderCancellationTokenSource.Token);
|
await RenderAsync(message.MailItemViewModel, renderCancellationTokenSource.Token);
|
||||||
@@ -909,7 +917,7 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
{
|
{
|
||||||
base.RegisterRecipients();
|
base.RegisterRecipients();
|
||||||
|
|
||||||
Messenger.Register<NewMailItemRenderingRequestedEvent>(this);
|
Messenger.Register<ReaderItemRefreshRequestedEvent>(this);
|
||||||
Messenger.Register<ThumbnailAdded>(this);
|
Messenger.Register<ThumbnailAdded>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -917,7 +925,7 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
{
|
{
|
||||||
base.UnregisterRecipients();
|
base.UnregisterRecipients();
|
||||||
|
|
||||||
Messenger.Unregister<NewMailItemRenderingRequestedEvent>(this);
|
Messenger.Unregister<ReaderItemRefreshRequestedEvent>(this);
|
||||||
Messenger.Unregister<ThumbnailAdded>(this);
|
Messenger.Unregister<ThumbnailAdded>(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
using Wino.Mail.ViewModels.Data;
|
|
||||||
|
|
||||||
namespace Wino.Mail.ViewModels.Messages;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// When the compose page is already active, but a different draft item is selected.
|
|
||||||
/// To not trigger navigation again and re-use existing WebView2 editor.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="MailItemViewModel">The new draft mail item to compose.</param>
|
|
||||||
public record NewComposeDraftItemRequestedEvent(MailItemViewModel MailItemViewModel);
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using Wino.Mail.ViewModels.Data;
|
|
||||||
|
|
||||||
namespace Wino.Mail.ViewModels.Messages;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// When the rendering page is active, but new item is requested to be rendered.
|
|
||||||
/// To not trigger navigation again and re-use existing Chromium.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="MailItemViewModel"></param>
|
|
||||||
public record NewMailItemRenderingRequestedEvent(MailItemViewModel MailItemViewModel);
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using Wino.Mail.ViewModels.Data;
|
||||||
|
|
||||||
|
namespace Wino.Mail.ViewModels.Messages;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Requests refreshing the currently active reader page (mail rendering or compose)
|
||||||
|
/// with a different selected mail item without re-navigation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="MailItemViewModel">The selected mail item to refresh with.</param>
|
||||||
|
public record ReaderItemRefreshRequestedEvent(MailItemViewModel MailItemViewModel);
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net.Mail;
|
using System.ComponentModel;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.WinUI;
|
using CommunityToolkit.WinUI;
|
||||||
@@ -30,20 +30,9 @@ public sealed partial class ImagePreviewControl : PersonPicture
|
|||||||
[GeneratedDependencyProperty]
|
[GeneratedDependencyProperty]
|
||||||
public partial IMailItemDisplayInformation? MailItemInformation { get; set; }
|
public partial IMailItemDisplayInformation? MailItemInformation { get; set; }
|
||||||
|
|
||||||
[GeneratedDependencyProperty]
|
|
||||||
public partial string? FromName { get; set; }
|
|
||||||
|
|
||||||
[GeneratedDependencyProperty]
|
|
||||||
public partial string? FromAddress { get; set; }
|
|
||||||
|
|
||||||
[GeneratedDependencyProperty]
|
|
||||||
public partial string? SenderContactPicture { get; set; }
|
|
||||||
|
|
||||||
[GeneratedDependencyProperty(DefaultValue = false)]
|
|
||||||
public partial bool ThumbnailUpdatedEvent { get; set; }
|
|
||||||
|
|
||||||
private readonly IThumbnailService? _thumbnailService;
|
private readonly IThumbnailService? _thumbnailService;
|
||||||
private readonly IPreferencesService? _preferencesService;
|
private readonly IPreferencesService? _preferencesService;
|
||||||
|
private INotifyPropertyChanged? _mailItemInformationPropertySource;
|
||||||
private CancellationTokenSource? _refreshCancellationTokenSource;
|
private CancellationTokenSource? _refreshCancellationTokenSource;
|
||||||
private CancellationTokenSource? _scheduledRefreshCancellationTokenSource;
|
private CancellationTokenSource? _scheduledRefreshCancellationTokenSource;
|
||||||
private long _refreshVersion;
|
private long _refreshVersion;
|
||||||
@@ -68,16 +57,20 @@ public sealed partial class ImagePreviewControl : PersonPicture
|
|||||||
|
|
||||||
partial void OnMailItemInformationPropertyChanged(DependencyPropertyChangedEventArgs e)
|
partial void OnMailItemInformationPropertyChanged(DependencyPropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
RequestRefresh();
|
if (_mailItemInformationPropertySource != null)
|
||||||
|
{
|
||||||
|
_mailItemInformationPropertySource.PropertyChanged -= MailItemInformationPropertyChanged;
|
||||||
|
_mailItemInformationPropertySource = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnFromNameChanged(string? newValue) => RequestRefresh();
|
if (e.NewValue is INotifyPropertyChanged observableMailItemInformation)
|
||||||
|
{
|
||||||
|
_mailItemInformationPropertySource = observableMailItemInformation;
|
||||||
|
_mailItemInformationPropertySource.PropertyChanged += MailItemInformationPropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
partial void OnFromAddressChanged(string? newValue) => RequestRefresh();
|
RequestRefresh();
|
||||||
|
}
|
||||||
partial void OnSenderContactPictureChanged(string? newValue) => RequestRefresh();
|
|
||||||
|
|
||||||
partial void OnThumbnailUpdatedEventChanged(bool newValue) => RequestRefresh();
|
|
||||||
|
|
||||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -86,10 +79,28 @@ public sealed partial class ImagePreviewControl : PersonPicture
|
|||||||
|
|
||||||
private void OnUnloaded(object sender, RoutedEventArgs e)
|
private void OnUnloaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (_mailItemInformationPropertySource != null)
|
||||||
|
{
|
||||||
|
_mailItemInformationPropertySource.PropertyChanged -= MailItemInformationPropertyChanged;
|
||||||
|
_mailItemInformationPropertySource = null;
|
||||||
|
}
|
||||||
|
|
||||||
CancelScheduledRefresh();
|
CancelScheduledRefresh();
|
||||||
CancelActiveRefresh();
|
CancelActiveRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void MailItemInformationPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
// Refresh only for fields that affect avatar image or initials.
|
||||||
|
if (string.IsNullOrEmpty(e.PropertyName)
|
||||||
|
|| e.PropertyName == nameof(IMailItemDisplayInformation.Base64ContactPicture)
|
||||||
|
|| e.PropertyName == nameof(IMailItemDisplayInformation.SenderContact)
|
||||||
|
|| e.PropertyName == nameof(IMailItemDisplayInformation.ThumbnailUpdatedEvent))
|
||||||
|
{
|
||||||
|
RequestRefresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void RequestRefresh()
|
private void RequestRefresh()
|
||||||
{
|
{
|
||||||
if (DispatcherQueue == null || DispatcherQueue.HasThreadAccess)
|
if (DispatcherQueue == null || DispatcherQueue.HasThreadAccess)
|
||||||
@@ -236,6 +247,9 @@ public sealed partial class ImagePreviewControl : PersonPicture
|
|||||||
|
|
||||||
private string ResolveAddress()
|
private string ResolveAddress()
|
||||||
{
|
{
|
||||||
|
if (MailItemInformation == null)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
var contactAddress = MailItemInformation?.SenderContact?.Address;
|
var contactAddress = MailItemInformation?.SenderContact?.Address;
|
||||||
if (!string.IsNullOrWhiteSpace(contactAddress))
|
if (!string.IsNullOrWhiteSpace(contactAddress))
|
||||||
return contactAddress.Trim();
|
return contactAddress.Trim();
|
||||||
@@ -243,7 +257,7 @@ public sealed partial class ImagePreviewControl : PersonPicture
|
|||||||
if (!string.IsNullOrWhiteSpace(MailItemInformation?.FromAddress))
|
if (!string.IsNullOrWhiteSpace(MailItemInformation?.FromAddress))
|
||||||
return MailItemInformation.FromAddress.Trim();
|
return MailItemInformation.FromAddress.Trim();
|
||||||
|
|
||||||
return FromAddress?.Trim() ?? string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ResolveDisplayName(string resolvedAddress)
|
private string ResolveDisplayName(string resolvedAddress)
|
||||||
@@ -255,9 +269,6 @@ public sealed partial class ImagePreviewControl : PersonPicture
|
|||||||
if (!string.IsNullOrWhiteSpace(MailItemInformation?.FromName))
|
if (!string.IsNullOrWhiteSpace(MailItemInformation?.FromName))
|
||||||
return MailItemInformation.FromName.Trim();
|
return MailItemInformation.FromName.Trim();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(FromName))
|
|
||||||
return FromName.Trim();
|
|
||||||
|
|
||||||
return resolvedAddress.Trim();
|
return resolvedAddress.Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,7 +280,7 @@ public sealed partial class ImagePreviewControl : PersonPicture
|
|||||||
if (!string.IsNullOrWhiteSpace(MailItemInformation?.Base64ContactPicture))
|
if (!string.IsNullOrWhiteSpace(MailItemInformation?.Base64ContactPicture))
|
||||||
return MailItemInformation.Base64ContactPicture;
|
return MailItemInformation.Base64ContactPicture;
|
||||||
|
|
||||||
return SenderContactPicture ?? string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ApplyInitialVisualStateAsync(string displayName, long refreshVersion, CancellationToken cancellationToken)
|
private async Task ApplyInitialVisualStateAsync(string displayName, long refreshVersion, CancellationToken cancellationToken)
|
||||||
@@ -387,16 +398,4 @@ public sealed partial class ImagePreviewControl : PersonPicture
|
|||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsValidEmail(string email)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_ = new MailAddress(email);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ public class NavigationService : NavigationServiceBase, INavigationService
|
|||||||
&& parameter is MailItemViewModel mailItemViewModel
|
&& parameter is MailItemViewModel mailItemViewModel
|
||||||
&& page != WinoPage.ComposePage)
|
&& page != WinoPage.ComposePage)
|
||||||
{
|
{
|
||||||
WeakReferenceMessenger.Default.Send(new NewMailItemRenderingRequestedEvent(mailItemViewModel));
|
WeakReferenceMessenger.Default.Send(new ReaderItemRefreshRequestedEvent(mailItemViewModel));
|
||||||
}
|
}
|
||||||
else if (listingFrame.Content != null
|
else if (listingFrame.Content != null
|
||||||
&& listingFrame.Content.GetType() == GetPageType(WinoPage.ComposePage)
|
&& listingFrame.Content.GetType() == GetPageType(WinoPage.ComposePage)
|
||||||
@@ -221,7 +221,7 @@ 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 NewComposeDraftItemRequestedEvent(composeDraftViewModel));
|
WeakReferenceMessenger.Default.Send(new ReaderItemRefreshRequestedEvent(composeDraftViewModel));
|
||||||
}
|
}
|
||||||
else if (listingFrame.Content != null
|
else if (listingFrame.Content != null
|
||||||
&& listingFrame.Content.GetType() == GetPageType(WinoPage.IdlePage)
|
&& listingFrame.Content.GetType() == GetPageType(WinoPage.IdlePage)
|
||||||
|
|||||||
@@ -43,10 +43,7 @@
|
|||||||
</Grid.ContextFlyout>-->
|
</Grid.ContextFlyout>-->
|
||||||
|
|
||||||
<Viewbox Width="24">
|
<Viewbox Width="24">
|
||||||
<controls:ImagePreviewControl
|
<PersonPicture DisplayName="{x:Bind Name, Mode=OneTime}" />
|
||||||
FromAddress="{x:Bind Address}"
|
|
||||||
FromName="{x:Bind Name}"
|
|
||||||
SenderContactPicture="{x:Bind Base64ContactPicture}" />
|
|
||||||
</Viewbox>
|
</Viewbox>
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
@@ -62,10 +59,7 @@
|
|||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<controls:ImagePreviewControl
|
<PersonPicture DisplayName="{x:Bind Name, Mode=OneTime}" />
|
||||||
FromAddress="{x:Bind Address}"
|
|
||||||
FromName="{x:Bind Name}"
|
|
||||||
SenderContactPicture="{x:Bind Base64ContactPicture}" />
|
|
||||||
<TextBlock Grid.Column="1">
|
<TextBlock Grid.Column="1">
|
||||||
<Run FontWeight="SemiBold" Text="{x:Bind Name}" /><LineBreak /><Run Text="{x:Bind Address}" />
|
<Run FontWeight="SemiBold" Text="{x:Bind Name}" /><LineBreak /><Run Text="{x:Bind Address}" />
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
@@ -485,8 +479,8 @@
|
|||||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind Subject}" />
|
<TextBlock FontWeight="SemiBold" Text="{x:Bind Subject}" />
|
||||||
<TextBlock FontSize="12" Text="{x:Bind Issuer}" />
|
<TextBlock FontSize="12" Text="{x:Bind Issuer}" />
|
||||||
<TextBlock>
|
<TextBlock>
|
||||||
<Run Text="{x:Bind domain:Translator.Composer_CertificateExpires}" />
|
<Run Text="{x:Bind domain:Translator.Composer_CertificateExpires, Mode=OneTime}" />
|
||||||
<Run Text="{x:Bind NotAfter, Mode=OneWay}" />
|
<Run Text="{x:Bind NotAfter, Mode=OneTime}" />
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ namespace Wino.Views.Mail;
|
|||||||
public sealed partial class ComposePage : ComposePageAbstract,
|
public sealed partial class ComposePage : ComposePageAbstract,
|
||||||
IRecipient<CreateNewComposeMailRequested>,
|
IRecipient<CreateNewComposeMailRequested>,
|
||||||
IRecipient<ApplicationThemeChanged>,
|
IRecipient<ApplicationThemeChanged>,
|
||||||
IRecipient<NewComposeDraftItemRequestedEvent>
|
IRecipient<ReaderItemRefreshRequestedEvent>
|
||||||
{
|
{
|
||||||
public WebView2 GetWebView() => WebViewEditor.GetUnderlyingWebView();
|
public WebView2 GetWebView() => WebViewEditor.GetUnderlyingWebView();
|
||||||
|
|
||||||
@@ -302,8 +302,10 @@ public sealed partial class ComposePage : ComposePageAbstract,
|
|||||||
WebViewEditor.IsEditorDarkMode = message.IsUnderlyingThemeDark;
|
WebViewEditor.IsEditorDarkMode = message.IsUnderlyingThemeDark;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IRecipient<NewComposeDraftItemRequestedEvent>.Receive(NewComposeDraftItemRequestedEvent message)
|
void IRecipient<ReaderItemRefreshRequestedEvent>.Receive(ReaderItemRefreshRequestedEvent message)
|
||||||
{
|
{
|
||||||
|
if (message.MailItemViewModel == null || !message.MailItemViewModel.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;
|
||||||
}
|
}
|
||||||
@@ -423,7 +425,7 @@ public sealed partial class ComposePage : ComposePageAbstract,
|
|||||||
|
|
||||||
WeakReferenceMessenger.Default.Register<CreateNewComposeMailRequested>(this);
|
WeakReferenceMessenger.Default.Register<CreateNewComposeMailRequested>(this);
|
||||||
WeakReferenceMessenger.Default.Register<ApplicationThemeChanged>(this);
|
WeakReferenceMessenger.Default.Register<ApplicationThemeChanged>(this);
|
||||||
WeakReferenceMessenger.Default.Register<NewComposeDraftItemRequestedEvent>(this);
|
WeakReferenceMessenger.Default.Register<ReaderItemRefreshRequestedEvent>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UnregisterRecipients()
|
protected override void UnregisterRecipients()
|
||||||
@@ -432,7 +434,7 @@ public sealed partial class ComposePage : ComposePageAbstract,
|
|||||||
|
|
||||||
WeakReferenceMessenger.Default.Unregister<CreateNewComposeMailRequested>(this);
|
WeakReferenceMessenger.Default.Unregister<CreateNewComposeMailRequested>(this);
|
||||||
WeakReferenceMessenger.Default.Unregister<ApplicationThemeChanged>(this);
|
WeakReferenceMessenger.Default.Unregister<ApplicationThemeChanged>(this);
|
||||||
WeakReferenceMessenger.Default.Unregister<NewComposeDraftItemRequestedEvent>(this);
|
WeakReferenceMessenger.Default.Unregister<ReaderItemRefreshRequestedEvent>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Save mime on closing the app.
|
// TODO: Save mime on closing the app.
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ public sealed partial class MailListPage : MailListPageAbstract,
|
|||||||
IRecipient<DisposeRenderingFrameRequested>
|
IRecipient<DisposeRenderingFrameRequested>
|
||||||
{
|
{
|
||||||
private const double RENDERING_COLUMN_MIN_WIDTH = 375;
|
private const double RENDERING_COLUMN_MIN_WIDTH = 375;
|
||||||
|
private const int IDLE_NAVIGATION_DELAY_MS = 120;
|
||||||
|
private int _idleNavigationRequestVersion = 0;
|
||||||
|
|
||||||
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)}");
|
||||||
@@ -80,6 +82,8 @@ public sealed partial class MailListPage : MailListPageAbstract,
|
|||||||
{
|
{
|
||||||
base.OnNavigatedFrom(e);
|
base.OnNavigatedFrom(e);
|
||||||
|
|
||||||
|
InvalidatePendingIdleNavigation();
|
||||||
|
|
||||||
this.Bindings.StopTracking();
|
this.Bindings.StopTracking();
|
||||||
|
|
||||||
ViewModel.MailCollection.ItemSelectionChanged -= WinoMailCollectionSelectionChanged;
|
ViewModel.MailCollection.ItemSelectionChanged -= WinoMailCollectionSelectionChanged;
|
||||||
@@ -280,17 +284,12 @@ public sealed partial class MailListPage : MailListPageAbstract,
|
|||||||
// No active mail item. Go to empty page.
|
// No active mail item. Go to empty page.
|
||||||
if (message.SelectedMailItemViewModel == null)
|
if (message.SelectedMailItemViewModel == null)
|
||||||
{
|
{
|
||||||
if (IsRenderingPageActive())
|
_ = NavigateIdleWhenSelectionSettlesAsync();
|
||||||
{
|
|
||||||
WeakReferenceMessenger.Default.Send(new CancelRenderingContentRequested());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure rendering frame actually navigates away from Compose/Rendering pages.
|
|
||||||
// Otherwise those pages keep their messenger registrations alive.
|
|
||||||
ViewModel.NavigationService.Navigate(WinoPage.IdlePage, null, NavigationReferenceFrame.RenderingFrame, NavigationTransitionType.DrillIn);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
InvalidatePendingIdleNavigation();
|
||||||
|
|
||||||
// Navigate to composing page.
|
// Navigate to composing page.
|
||||||
if (message.SelectedMailItemViewModel.IsDraft)
|
if (message.SelectedMailItemViewModel.IsDraft)
|
||||||
{
|
{
|
||||||
@@ -309,7 +308,7 @@ public sealed partial class MailListPage : MailListPageAbstract,
|
|||||||
{
|
{
|
||||||
// Composer is already active. Skip connected animation since the page
|
// Composer is already active. Skip connected animation since the page
|
||||||
// will be reused in-place (no navigation occurs).
|
// will be reused in-place (no navigation occurs).
|
||||||
// NavigationService will send NewComposeDraftItemRequestedEvent instead.
|
// NavigationService will send ReaderItemRefreshRequestedEvent instead.
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
composerPageTransition = NavigationTransitionType.DrillIn;
|
composerPageTransition = NavigationTransitionType.DrillIn;
|
||||||
@@ -337,6 +336,34 @@ 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 InvalidatePendingIdleNavigation()
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
_idleNavigationRequestVersion++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task NavigateIdleWhenSelectionSettlesAsync()
|
||||||
|
{
|
||||||
|
int requestVersion = ++_idleNavigationRequestVersion;
|
||||||
|
|
||||||
|
await Task.Delay(IDLE_NAVIGATION_DELAY_MS);
|
||||||
|
|
||||||
|
if (requestVersion != _idleNavigationRequestVersion) return;
|
||||||
|
if (ViewModel.MailCollection.SelectedItemsCount != 0) return;
|
||||||
|
|
||||||
|
if (IsRenderingPageActive())
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Send(new CancelRenderingContentRequested());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure rendering frame actually navigates away from Compose/Rendering pages.
|
||||||
|
// Otherwise those pages keep their messenger registrations alive.
|
||||||
|
ViewModel.NavigationService.Navigate(WinoPage.IdlePage, null, NavigationReferenceFrame.RenderingFrame, NavigationTransitionType.DrillIn);
|
||||||
|
UpdateAdaptiveness();
|
||||||
|
}
|
||||||
|
|
||||||
private void PrepareComposePageWebViewTransition()
|
private void PrepareComposePageWebViewTransition()
|
||||||
{
|
{
|
||||||
var webView = GetComposerPageWebView();
|
var webView = GetComposerPageWebView();
|
||||||
|
|||||||
@@ -46,10 +46,7 @@
|
|||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
Width="36"
|
Width="36"
|
||||||
Height="36"
|
Height="36"
|
||||||
FromAddress="{x:Bind Address}"
|
MailItemInformation="{x:Bind}" />
|
||||||
FromName="{x:Bind Name}"
|
|
||||||
SenderContactPicture="{x:Bind Base64ContactPicture}"
|
|
||||||
ThumbnailUpdatedEvent="{x:Bind ThumbnailUpdatedEvent, Mode=OneWay}" />
|
|
||||||
|
|
||||||
<TextBlock Grid.Column="1" Text="{x:Bind Name}" />
|
<TextBlock Grid.Column="1" Text="{x:Bind Name}" />
|
||||||
|
|
||||||
@@ -291,9 +288,7 @@
|
|||||||
Width="36"
|
Width="36"
|
||||||
Height="36"
|
Height="36"
|
||||||
FontSize="16"
|
FontSize="16"
|
||||||
FromAddress="{x:Bind ViewModel.FromAddress, Mode=OneWay}"
|
MailItemInformation="{x:Bind ViewModel.CurrentMailItemDisplayInformation, Mode=OneWay}" />
|
||||||
FromName="{x:Bind ViewModel.FromName, Mode=OneWay}"
|
|
||||||
SenderContactPicture="{x:Bind ViewModel.ContactPicture, Mode=OneWay}" />
|
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
|
|||||||
@@ -164,16 +164,18 @@ 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);
|
||||||
|
Chromium.CoreWebView2Initialized -= CoreWebViewInitialized;
|
||||||
|
Chromium.CoreWebView2Initialized += CoreWebViewInitialized;
|
||||||
|
_ = Chromium.EnsureCoreWebView2Async();
|
||||||
|
|
||||||
base.OnNavigatedTo(e);
|
base.OnNavigatedTo(e);
|
||||||
|
|
||||||
var anim = ConnectedAnimationService.GetForCurrentView().GetAnimation("WebViewConnectedAnimation");
|
var anim = ConnectedAnimationService.GetForCurrentView().GetAnimation("WebViewConnectedAnimation");
|
||||||
anim?.TryStart(Chromium);
|
anim?.TryStart(Chromium);
|
||||||
|
|
||||||
Chromium.CoreWebView2Initialized -= CoreWebViewInitialized;
|
|
||||||
Chromium.CoreWebView2Initialized += CoreWebViewInitialized;
|
|
||||||
|
|
||||||
_ = Chromium.EnsureCoreWebView2Async();
|
|
||||||
|
|
||||||
// We don't have shell initialized here. It's only standalone EML viewing.
|
// We don't have shell initialized here. It's only standalone EML viewing.
|
||||||
// Shift command bar from top to adjust the design.
|
// Shift command bar from top to adjust the design.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user