From 39626e0df965b82f0333daa60d16cba32586219e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Wed, 26 Jun 2024 20:00:10 +0200 Subject: [PATCH] Couple UI Fixes (#255) - Disabled UI navigation cache for all pages. - Restore the renderer<>composer page animation back. - IdlePage functionality into mail list page. - Couple bugfixes. --- Wino.Mail.ViewModels/ComposePageViewModel.cs | 18 ++-- Wino.Mail.ViewModels/IdlePageViewModel.cs | 33 +------ Wino.Mail.ViewModels/MailListPageViewModel.cs | 35 ++++++-- .../MailRenderingPageViewModel.cs | 7 +- Wino.Mail/App.xaml.cs | 3 - Wino.Mail/Services/WinoNavigationService.cs | 16 ++++ Wino.Mail/Views/ComposePage.xaml.cs | 11 ++- Wino.Mail/Views/IdlePage.xaml | 19 +---- Wino.Mail/Views/MailListPage.xaml | 27 ++++-- Wino.Mail/Views/MailListPage.xaml.cs | 85 +++++++++++-------- Wino.Mail/Views/MailRenderingPage.xaml.cs | 31 ++++++- Wino.Mail/Wino.Mail.csproj | 4 +- 12 files changed, 180 insertions(+), 109 deletions(-) diff --git a/Wino.Mail.ViewModels/ComposePageViewModel.cs b/Wino.Mail.ViewModels/ComposePageViewModel.cs index 2e083e74..5b1613f9 100644 --- a/Wino.Mail.ViewModels/ComposePageViewModel.cs +++ b/Wino.Mail.ViewModels/ComposePageViewModel.cs @@ -225,7 +225,14 @@ namespace Wino.Mail.ViewModels private async Task SaveBodyAsync() { if (GetHTMLBodyFunction != null) - bodyBuilder.HtmlBody = Regex.Unescape(await GetHTMLBodyFunction()); + { + var htmlBody = await GetHTMLBodyFunction(); + + if (!string.IsNullOrEmpty(htmlBody)) + { + bodyBuilder.HtmlBody = Regex.Unescape(htmlBody); + } + } if (!string.IsNullOrEmpty(bodyBuilder.HtmlBody)) bodyBuilder.TextBody = HtmlAgilityPackExtensions.GetPreviewText(bodyBuilder.HtmlBody); @@ -269,8 +276,6 @@ namespace Wino.Mail.ViewModels base.OnNavigatedFrom(mode, parameters); await UpdateMimeChangesAsync().ConfigureAwait(false); - - await ExecuteUIThread(() => { _statePersistanceService.IsReadingMail = false; }); } public override async void OnNavigatedTo(NavigationMode mode, object parameters) @@ -289,8 +294,6 @@ namespace Wino.Mail.ViewModels ToItems.CollectionChanged -= ContactListCollectionChanged; ToItems.CollectionChanged += ContactListCollectionChanged; - _statePersistanceService.IsReadingMail = true; - // Check if there is any delivering mail address from protocol launch. if (_launchProtocolService.MailtoParameters != null) @@ -388,6 +391,11 @@ namespace Wino.Mail.ViewModels return; } + catch (IOException) + { + DialogService.InfoBarMessage("Busy", "Mail is being processed. Please wait a moment and try again.", InfoBarMessageType.Warning); + } + catch (ComposerMimeNotFoundException) { DialogService.InfoBarMessage(Translator.Info_ComposerMissingMIMETitle, Translator.Info_ComposerMissingMIMEMessage, InfoBarMessageType.Error); diff --git a/Wino.Mail.ViewModels/IdlePageViewModel.cs b/Wino.Mail.ViewModels/IdlePageViewModel.cs index a164f85b..4bc6dcb8 100644 --- a/Wino.Mail.ViewModels/IdlePageViewModel.cs +++ b/Wino.Mail.ViewModels/IdlePageViewModel.cs @@ -1,38 +1,9 @@ -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Messaging; -using Wino.Core.Domain; -using Wino.Core.Domain.Interfaces; -using Wino.Core.Domain.Models.Navigation; -using Wino.Core.Messages.Mails; +using Wino.Core.Domain.Interfaces; namespace Wino.Mail.ViewModels { - public partial class IdlePageViewModel : BaseViewModel, IRecipient + public partial class IdlePageViewModel : BaseViewModel { - [ObservableProperty] - [NotifyPropertyChangedFor(nameof(HasSelectedItems))] - [NotifyPropertyChangedFor(nameof(SelectedMessageText))] - private int selectedItemCount; - - public bool HasSelectedItems => SelectedItemCount > 0; - - public string SelectedMessageText => HasSelectedItems ? string.Format(Translator.MailsSelected, SelectedItemCount) : Translator.NoMailSelected; - public IdlePageViewModel(IDialogService dialogService) : base(dialogService) { } - - public void Receive(SelectedMailItemsChanged message) - { - SelectedItemCount = message.SelectedItemCount; - } - - public override void OnNavigatedTo(NavigationMode mode, object parameters) - { - base.OnNavigatedTo(mode, parameters); - - if (parameters != null && parameters is int selectedItemCount) - SelectedItemCount = selectedItemCount; - else - SelectedItemCount = 0; - } } } diff --git a/Wino.Mail.ViewModels/MailListPageViewModel.cs b/Wino.Mail.ViewModels/MailListPageViewModel.cs index a724d09d..2a60e6e1 100644 --- a/Wino.Mail.ViewModels/MailListPageViewModel.cs +++ b/Wino.Mail.ViewModels/MailListPageViewModel.cs @@ -24,6 +24,7 @@ using Wino.Core.Domain.Models.Menus; using Wino.Core.Domain.Models.Reader; using Wino.Core.Domain.Models.Synchronization; using Wino.Core.Messages.Mails; +using Wino.Core.Messages.Shell; using Wino.Core.Messages.Synchronization; using Wino.Mail.ViewModels.Collections; using Wino.Mail.ViewModels.Data; @@ -38,7 +39,8 @@ namespace Wino.Mail.ViewModels IRecipient, IRecipient, IRecipient, - IRecipient + IRecipient, + IRecipient { private bool isChangingFolder = false; @@ -93,6 +95,9 @@ namespace Wino.Mail.ViewModels private FolderPivotViewModel _selectedFolderPivot; + [ObservableProperty] + private bool isMultiSelectionModeEnabled; + [ObservableProperty] private string searchQuery; @@ -207,9 +212,11 @@ namespace Wino.Mail.ViewModels public bool IsFolderSynchronizationEnabled => ActiveFolder?.IsSynchronizationEnabled ?? false; public int SelectedItemCount => SelectedItems.Count; public bool HasMultipleItemSelections => SelectedItemCount > 1; + public bool HasSingleItemSelection => SelectedItemCount == 1; public bool HasSelectedItems => SelectedItems.Any(); public bool IsArchiveSpecialFolder => ActiveFolder?.SpecialFolderType == SpecialFolderType.Archive; + public string SelectedMessageText => HasSelectedItems ? string.Format(Translator.MailsSelected, SelectedItemCount) : Translator.NoMailSelected; /// /// Indicates current state of the mail list. Doesn't matter it's loading or no. /// @@ -231,14 +238,21 @@ namespace Wino.Mail.ViewModels { if (_activeMailItem == selectedMailItemViewModel) return; - // Don't update active mail item if Ctrl key is pressed. + // Don't update active mail item if Ctrl key is pressed or multi selection is ennabled. // User is probably trying to select multiple items. // This is not the same behavior in Windows Mail, // but it's a trash behavior. var isCtrlKeyPressed = _keyPressService.IsCtrlKeyPressed(); - if (isCtrlKeyPressed) return; + bool isMultiSelecting = isCtrlKeyPressed || IsMultiSelectionModeEnabled; + + if (isMultiSelecting ? StatePersistanceService.IsReaderNarrowed : false) + { + // Don't change the active mail item if the reader is narrowed, but just update the shell. + Messenger.Send(new ShellStateUpdated()); + return; + } _activeMailItem = selectedMailItemViewModel; @@ -271,6 +285,8 @@ namespace Wino.Mail.ViewModels public void NotifyItemSelected() { + OnPropertyChanged(nameof(SelectedMessageText)); + OnPropertyChanged(nameof(HasSingleItemSelection)); OnPropertyChanged(nameof(HasSelectedItems)); OnPropertyChanged(nameof(SelectedItemCount)); OnPropertyChanged(nameof(HasMultipleItemSelections)); @@ -812,14 +828,21 @@ namespace Wino.Mail.ViewModels } #region Receivers + void IRecipient.Receive(MailItemSelectedEvent message) - => SelectedItems.Add(message.SelectedMailItem); + { + if (!SelectedItems.Contains(message.SelectedMailItem)) SelectedItems.Add(message.SelectedMailItem); + } void IRecipient.Receive(MailItemSelectionRemovedEvent message) - => SelectedItems.Remove(message.RemovedMailItem); + { + if (SelectedItems.Contains(message.RemovedMailItem)) SelectedItems.Remove(message.RemovedMailItem); + } async void IRecipient.Receive(ActiveMailFolderChangedEvent message) { + NotifyItemSelected(); + isChangingFolder = true; ActiveFolder = message.BaseFolderMenuItem; @@ -963,5 +986,7 @@ namespace Wino.Mail.ViewModels await ExecuteUIThread(() => { IsAccountSynchronizerInSynchronization = isAnyAccountSynchronizing; }); } + + public void Receive(SelectedMailItemsChanged message) => NotifyItemSelected(); } } diff --git a/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs b/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs index df3dac28..919edccc 100644 --- a/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs +++ b/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs @@ -460,8 +460,13 @@ namespace Wino.Mail.ViewModels initializedMailItemViewModel = null; initializedMimeMessageInformation = null; - StatePersistanceService.IsReadingMail = false; forceImageLoading = false; + + ToItems.Clear(); + CCItemsItems.Clear(); + BCCItems.Clear(); + Attachments.Clear(); + MenuItems.Clear(); } private void LoadAddressInfo(InternetAddressList list, ObservableCollection collection) diff --git a/Wino.Mail/App.xaml.cs b/Wino.Mail/App.xaml.cs index 359a8176..f99f32be 100644 --- a/Wino.Mail/App.xaml.cs +++ b/Wino.Mail/App.xaml.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using System.Threading; using System.Threading.Tasks; using Microsoft.AppCenter; using Microsoft.AppCenter.Analytics; @@ -12,9 +11,7 @@ using Serilog; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; using Windows.ApplicationModel.Core; -using Windows.ApplicationModel.Resources; using Windows.Foundation.Metadata; -using Windows.Globalization; using Windows.Storage; using Windows.System.Profile; using Windows.UI; diff --git a/Wino.Mail/Services/WinoNavigationService.cs b/Wino.Mail/Services/WinoNavigationService.cs index cadd6b02..bae730ac 100644 --- a/Wino.Mail/Services/WinoNavigationService.cs +++ b/Wino.Mail/Services/WinoNavigationService.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using CommunityToolkit.Mvvm.Messaging; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -18,6 +19,14 @@ namespace Wino.Services { public class WinoNavigationService : IWinoNavigationService { + private readonly IStatePersistanceService _statePersistanceService; + + private WinoPage[] _renderingPageTypes = new WinoPage[] + { + WinoPage.MailRenderingPage, + WinoPage.ComposePage + }; + private Frame GetCoreFrame(NavigationReferenceFrame frameType) { if (Window.Current.Content is Frame appFrame && appFrame.Content is AppShell shellPage) @@ -36,6 +45,11 @@ namespace Wino.Services } } + public WinoNavigationService(IStatePersistanceService statePersistanceService) + { + _statePersistanceService = statePersistanceService; + } + private Type GetPageType(WinoPage winoPage) { switch (winoPage) @@ -85,6 +99,8 @@ namespace Wino.Services var pageType = GetPageType(page); Frame shellFrame = GetCoreFrame(NavigationReferenceFrame.ShellFrame); + _statePersistanceService.IsReadingMail = _renderingPageTypes.Contains(page); + if (shellFrame != null) { var currentFrameType = GetCurrentFrameType(ref shellFrame); diff --git a/Wino.Mail/Views/ComposePage.xaml.cs b/Wino.Mail/Views/ComposePage.xaml.cs index 4c3df606..35b27bd6 100644 --- a/Wino.Mail/Views/ComposePage.xaml.cs +++ b/Wino.Mail/Views/ComposePage.xaml.cs @@ -46,8 +46,6 @@ namespace Wino.Views } public static readonly DependencyProperty IsComposerDarkModeProperty = DependencyProperty.Register(nameof(IsComposerDarkMode), typeof(bool), typeof(ComposePage), new PropertyMetadata(false, OnIsComposerDarkModeChanged)); - - public WebView2 GetWebView() => Chromium; private TaskCompletionSource DOMLoadedTask = new TaskCompletionSource(); @@ -336,6 +334,12 @@ namespace Wino.Views base.OnNavigatingFrom(e); DisposeDisposables(); + DisposeWebView2(); + } + + private void DisposeWebView2() + { + if (Chromium == null) return; Chromium.CoreWebView2Initialized -= ChromiumInitialized; @@ -344,6 +348,9 @@ namespace Wino.Views Chromium.CoreWebView2.DOMContentLoaded -= DOMLoaded; Chromium.CoreWebView2.WebMessageReceived -= ScriptMessageRecieved; } + + Chromium.Close(); + GC.Collect(); } private void DisposeDisposables() diff --git a/Wino.Mail/Views/IdlePage.xaml b/Wino.Mail/Views/IdlePage.xaml index 7ca48a84..101fdc4d 100644 --- a/Wino.Mail/Views/IdlePage.xaml +++ b/Wino.Mail/Views/IdlePage.xaml @@ -9,22 +9,7 @@ xmlns:controls="using:Wino.Controls" mc:Ignorable="d"> - - - + + - - - - - diff --git a/Wino.Mail/Views/MailListPage.xaml b/Wino.Mail/Views/MailListPage.xaml index badaf85e..5ea7e835 100644 --- a/Wino.Mail/Views/MailListPage.xaml +++ b/Wino.Mail/Views/MailListPage.xaml @@ -20,6 +20,7 @@ xmlns:ui="using:Microsoft.Toolkit.Uwp.UI" xmlns:viewModelData="using:Wino.Mail.ViewModels.Data" xmlns:wino="using:Wino" + xmlns:converters="using:Wino.Converters" x:Name="root" Loaded="MailListPageLoaded" mc:Ignorable="d"> @@ -478,6 +479,7 @@ x:Name="SelectionModeToggle" Grid.Column="1" Checked="SelectionModeToggleChecked" + IsChecked="{x:Bind ViewModel.IsMultiSelectionModeEnabled, Mode=TwoWay}" Style="{StaticResource TopCommandBarToggleButtonStyle}" Unchecked="SelectionModeToggleUnchecked"> @@ -807,13 +809,28 @@ - - + + + + + + + + + + diff --git a/Wino.Mail/Views/MailListPage.xaml.cs b/Wino.Mail/Views/MailListPage.xaml.cs index 1ba6fb81..bd6bf7ff 100644 --- a/Wino.Mail/Views/MailListPage.xaml.cs +++ b/Wino.Mail/Views/MailListPage.xaml.cs @@ -44,6 +44,7 @@ namespace Wino.Views private IStatePersistanceService StatePersistanceService { get; } = App.Current.Services.GetService(); private IPreferencesService PreferencesService { get; } = App.Current.Services.GetService(); + private IKeyPressService KeyPressService { get; } = App.Current.Services.GetService(); public MailListPage() { @@ -67,14 +68,10 @@ namespace Wino.Views { base.OnNavigatedFrom(e); - // Force to go to empty page to dispose compose or rendering. - if (!(RenderingFrame.Content is IdlePage)) - { - RenderingFrame.Navigate(typeof(IdlePage), null); - } - this.Bindings.StopTracking(); + RenderingFrame.Navigate(typeof(IdlePage)); + GC.Collect(); } @@ -153,35 +150,61 @@ namespace Wino.Views private void UpdateAdaptiveness() { - var readerGridVisibility = !(StatePersistanceService.IsReadingMail && StatePersistanceService.IsReaderNarrowed) ? Visibility.Visible : Visibility.Collapsed; - ReaderGridContainer.Visibility = readerGridVisibility; - ReaderGrid.Visibility = readerGridVisibility; - RenderingFrame.Visibility = StatePersistanceService.IsReadingMail ? Visibility.Visible : (StatePersistanceService.IsReaderNarrowed ? Visibility.Collapsed : Visibility.Visible); - if (RenderingFrame.Visibility == Visibility.Collapsed) + bool shouldDisplayNoMessagePanel, shouldDisplayMailingList, shouldDisplayRenderingFrame; + + // This is the smallest state UI can get. + // Either mailing list or rendering grid is visible. + if (StatePersistanceService.IsReaderNarrowed) { - Grid.SetColumn(ReaderGrid, 0); - Grid.SetColumnSpan(ReaderGrid, 2); - Grid.SetColumn(ReaderGridContainer, 0); - Grid.SetColumnSpan(ReaderGridContainer, 2); + // Start visibility checks by no message panel. + + bool isMultiSelectionEnabled = ViewModel.IsMultiSelectionModeEnabled || KeyPressService.IsCtrlKeyPressed(); + + shouldDisplayMailingList = isMultiSelectionEnabled ? true : (!ViewModel.HasSelectedItems || ViewModel.HasMultipleItemSelections); + shouldDisplayNoMessagePanel = shouldDisplayMailingList ? false : !ViewModel.HasSelectedItems || ViewModel.HasMultipleItemSelections; + shouldDisplayRenderingFrame = shouldDisplayMailingList ? false : !shouldDisplayNoMessagePanel; } else { - Grid.SetColumn(ReaderGrid, 0); - Grid.SetColumnSpan(ReaderGrid, 1); + shouldDisplayMailingList = true; + shouldDisplayNoMessagePanel = !ViewModel.HasSelectedItems || ViewModel.HasMultipleItemSelections; + shouldDisplayRenderingFrame = !shouldDisplayNoMessagePanel; + } + + ReaderGridContainer.Visibility = shouldDisplayMailingList ? Visibility.Visible : Visibility.Collapsed; + RenderingFrame.Visibility = shouldDisplayRenderingFrame ? Visibility.Visible : Visibility.Collapsed; + NoMailSelectedPanel.Visibility = shouldDisplayNoMessagePanel ? Visibility.Visible : Visibility.Collapsed; + + if (StatePersistanceService.IsReaderNarrowed) + { + if (RenderingFrame.Visibility == Visibility.Visible && ReaderGridContainer.Visibility == Visibility.Collapsed) + { + // Extend rendering frame to full width. + Grid.SetColumn(RenderingGrid, 0); + Grid.SetColumnSpan(RenderingGrid, 2); + + Grid.SetColumn(ReaderGrid, 0); + Grid.SetColumnSpan(ReaderGrid, 2); + } + else if (RenderingFrame.Visibility == Visibility.Collapsed && NoMailSelectedPanel.Visibility == Visibility.Collapsed) + { + // Only mail list is available. + // Extend the mailing list. + Grid.SetColumn(ReaderGridContainer, 0); + Grid.SetColumnSpan(ReaderGridContainer, 2); + } + } + else + { + // Mailing list is always visible on the first part. + Grid.SetColumn(ReaderGridContainer, 0); Grid.SetColumnSpan(ReaderGridContainer, 1); - } - if (ReaderGrid.Visibility == Visibility.Collapsed) - { - Grid.SetColumn(RenderingFrame, 0); - Grid.SetColumnSpan(RenderingFrame, 2); - } - else - { - Grid.SetColumn(RenderingFrame, 1); - Grid.SetColumnSpan(RenderingFrame, 1); + // Rendering grid should take the rest of the space. + Grid.SetColumn(RenderingGrid, 1); + Grid.SetColumnSpan(RenderingGrid, 1); } } @@ -263,8 +286,6 @@ namespace Wino.Views if (message.SelectedMailItemViewModel == null) { WeakReferenceMessenger.Default.Send(new CancelRenderingContentRequested()); - - ViewModel.NavigationService.Navigate(WinoPage.IdlePage, ViewModel.SelectedItemCount, NavigationReferenceFrame.RenderingFrame); } else { @@ -298,7 +319,6 @@ namespace Wino.Views if (message.SelectedMailItemViewModel == null) return; - if (IsComposingPageActive()) { PrepareComposePageWebViewTransition(); @@ -358,11 +378,6 @@ namespace Wino.Views public void Receive(ActiveMailFolderChangedEvent message) { - if (!(RenderingFrame.Content is IdlePage)) - { - RenderingFrame.Navigate(typeof(IdlePage), null); - } - UpdateAdaptiveness(); } diff --git a/Wino.Mail/Views/MailRenderingPage.xaml.cs b/Wino.Mail/Views/MailRenderingPage.xaml.cs index 0b09fae0..9cb2243f 100644 --- a/Wino.Mail/Views/MailRenderingPage.xaml.cs +++ b/Wino.Mail/Views/MailRenderingPage.xaml.cs @@ -35,6 +35,8 @@ namespace Wino.Views private bool isRenderingInProgress = false; private TaskCompletionSource DOMLoadedTask = new TaskCompletionSource(); + private bool isChromiumDisposed = false; + public WebView2 GetWebView() => Chromium; public MailRenderingPage() @@ -43,7 +45,6 @@ namespace Wino.Views Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF"); Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation"); - NavigationCacheMode = NavigationCacheMode.Enabled; } public override async void OnEditorThemeChanged() @@ -77,7 +78,7 @@ namespace Wino.Views } script += ");"; - return await Chromium.ExecuteScriptAsync(script); + return isChromiumDisposed ? string.Empty : await Chromium.ExecuteScriptAsync(script); } private async Task RenderInternalAsync(string htmlBody) @@ -129,9 +130,33 @@ namespace Wino.Views protected override void OnNavigatedFrom(NavigationEventArgs e) { + base.OnNavigatedFrom(e); + WeakReferenceMessenger.Default.Send(new CancelRenderingContentRequested()); - base.OnNavigatedFrom(e); + // Disposing the page. + // Make sure the WebView2 is disposed properly. + + DisposeWebView2(); + } + + private void DisposeWebView2() + { + if (Chromium == null) return; + + Chromium.CoreWebView2Initialized -= CoreWebViewInitialized; + Chromium.NavigationStarting -= WebViewNavigationStarting; + + if (Chromium.CoreWebView2 != null) + { + Chromium.CoreWebView2.DOMContentLoaded -= DOMContentLoaded; + Chromium.CoreWebView2.NewWindowRequested -= WindowRequested; + } + + isChromiumDisposed = true; + + Chromium.Close(); + GC.Collect(); } protected override void OnNavigatedTo(NavigationEventArgs e) diff --git a/Wino.Mail/Wino.Mail.csproj b/Wino.Mail/Wino.Mail.csproj index 8796dfd4..5ac3fdd4 100644 --- a/Wino.Mail/Wino.Mail.csproj +++ b/Wino.Mail/Wino.Mail.csproj @@ -588,8 +588,8 @@ MSBuild:Compile - Designer MSBuild:Compile + Designer Designer @@ -895,4 +895,4 @@ --> - \ No newline at end of file +