Simplified compoper and rendering logic through messages.

This commit is contained in:
Burak Kaan Köse
2026-02-25 01:41:48 +01:00
parent 5d46ea73db
commit 3a39266121
14 changed files with 157 additions and 108 deletions
+6 -4
View File
@@ -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);
+36 -37
View File
@@ -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)
+4 -10
View File
@@ -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.