diff --git a/Directory.Packages.props b/Directory.Packages.props index 710751ef..388751b7 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,56 +15,56 @@ - + - + - - - - + + + + - - + + - + - - + + - + - + - - + + - + - - - - + + + + - + - - - + + + - - + + - \ No newline at end of file + diff --git a/Wino.Core.Domain/Models/MailItem/MailListInitializationOptions.cs b/Wino.Core.Domain/Models/MailItem/MailListInitializationOptions.cs index 033ec012..c938941f 100644 --- a/Wino.Core.Domain/Models/MailItem/MailListInitializationOptions.cs +++ b/Wino.Core.Domain/Models/MailItem/MailListInitializationOptions.cs @@ -12,5 +12,7 @@ public record MailListInitializationOptions(IEnumerable Folders bool CreateThreads, bool? IsFocusedOnly, string SearchQuery, - IEnumerable ExistingUniqueIds, - List PreFetchMailCopies = null); + HashSet ExistingUniqueIds = null, + List PreFetchMailCopies = null, + int Skip = 0, + int Take = 0); diff --git a/Wino.Core.Domain/Wino.Core.Domain.csproj b/Wino.Core.Domain/Wino.Core.Domain.csproj index f34bd83b..35882868 100644 --- a/Wino.Core.Domain/Wino.Core.Domain.csproj +++ b/Wino.Core.Domain/Wino.Core.Domain.csproj @@ -1,6 +1,6 @@  - net9.0 + net10.0 win-x86;win-x64;win-arm64 true x86;x64;arm64 @@ -57,7 +57,6 @@ - diff --git a/Wino.Core.WinUI/Helpers/XamlHelpers.cs b/Wino.Core.WinUI/Helpers/XamlHelpers.cs index b2318e7d..3bb6f8d0 100644 --- a/Wino.Core.WinUI/Helpers/XamlHelpers.cs +++ b/Wino.Core.WinUI/Helpers/XamlHelpers.cs @@ -26,6 +26,7 @@ public static class XamlHelpers #region Converters + public static Thickness GetMailItemControlMargin(bool isDisplayedInThread) => isDisplayedInThread ? new Thickness(40, 0, 6, 0) : new Thickness(6, 0, 6, 0); public static bool IsMultiple(int count) => count > 1; public static bool ReverseIsMultiple(int count) => count < 1; public static PopupPlacementMode GetPlaccementModeForCalendarType(CalendarDisplayType type) diff --git a/Wino.Mail.ViewModels/Collections/GroupedEmailCollection.cs b/Wino.Mail.ViewModels/Collections/GroupedEmailCollection.cs index 72b5bb6e..96076530 100644 --- a/Wino.Mail.ViewModels/Collections/GroupedEmailCollection.cs +++ b/Wino.Mail.ViewModels/Collections/GroupedEmailCollection.cs @@ -39,6 +39,7 @@ public partial class GroupedEmailCollection : ObservableObject, IRecipient _groupHeaderIndexCache = []; private readonly Dictionary> _groupItems = []; private readonly Dictionary _threadExpanders = []; + private readonly HashSet _mailCopyIdHashSet = []; private bool _disposed; private bool _isUpdating; @@ -78,6 +79,11 @@ public partial class GroupedEmailCollection : ObservableObject, IRecipient public int TotalUnreadCount => _sourceItems.Count(e => e.MailCopy?.IsRead == false); + /// + /// HashSet containing unique IDs of all mail copies in the collection for pagination tracking + /// + public HashSet MailCopyIdHashSet => _mailCopyIdHashSet; + /// /// Gets all email items across all groups as a flat collection /// @@ -227,6 +233,9 @@ public partial class GroupedEmailCollection : ObservableObject, IRecipient public IReadOnlyList ThreadEmails => _threadEmails.AsReadOnly(); + public MailItemViewModel LatestMailViewModel => _threadEmails.OrderByDescending(e => e.MailCopy?.CreationDate).FirstOrDefault()!; + public ThreadMailItemViewModel(string threadId) { _threadId = threadId; } - partial void OnIsSelectedChanged(bool value) - { - - } - protected virtual void Dispose(bool disposing) { if (_disposed) @@ -86,6 +83,8 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IDisposable { OnPropertyChanged(nameof(Subject)); OnPropertyChanged(nameof(FromName)); + OnPropertyChanged(nameof(LatestEmailDate)); + OnPropertyChanged(nameof(LatestMailViewModel)); } diff --git a/Wino.Mail.ViewModels/MailListPageViewModel.cs b/Wino.Mail.ViewModels/MailListPageViewModel.cs index c2f8e386..7b2c7cf9 100644 --- a/Wino.Mail.ViewModels/MailListPageViewModel.cs +++ b/Wino.Mail.ViewModels/MailListPageViewModel.cs @@ -60,14 +60,6 @@ public partial class MailListPageViewModel : MailBaseViewModel, private IObservable> selectionChangedObservable = null; public GroupedEmailCollection MailCollection { get; set; } = new GroupedEmailCollection(); - - //public IEnumerable SelectedItems - //{ - // get - // { - - // } - //} public ObservableCollection SelectedItems { get; set; } = []; public ObservableCollection PivotFolders { get; set; } = []; public ObservableCollection ActionItems { get; set; } = []; @@ -246,11 +238,10 @@ public partial class MailListPageViewModel : MailBaseViewModel, { if (SetProperty(ref _selectedSortingOption, value)) { - // TODO: Update sorting in mail collection. - //if (value != null && MailCollection != null) - //{ - // MailCollection.SortingType = value.Type; - //} + if (value != null && MailCollection != null) + { + MailCollection.GroupingType = value.Type == SortingOptionType.ReceiveDate ? EmailGroupingType.ByDate : EmailGroupingType.ByFromName; + } } } } @@ -562,24 +553,24 @@ public partial class MailListPageViewModel : MailBaseViewModel, [RelayCommand] private async Task LoadMoreItemsAsync() { - //if (IsInitializingFolder || IsOnlineSearchEnabled) return; + if (IsInitializingFolder || IsOnlineSearchEnabled) return; - //await ExecuteUIThread(() => { IsInitializingFolder = true; }); + await ExecuteUIThread(() => { IsInitializingFolder = true; }); - //var initializationOptions = new MailListInitializationOptions(ActiveFolder.HandlingFolders, - // SelectedFilterOption.Type, - // SelectedSortingOption.Type, - // PreferencesService.IsThreadingEnabled, - // SelectedFolderPivot.IsFocused, - // IsInSearchMode ? SearchQuery : string.Empty, - // MailCollection.MailCopyIdHashSet); + var initializationOptions = new MailListInitializationOptions(ActiveFolder.HandlingFolders, + SelectedFilterOption.Type, + SelectedSortingOption.Type, + PreferencesService.IsThreadingEnabled, + SelectedFolderPivot.IsFocused, + IsInSearchMode ? SearchQuery : string.Empty, + MailCollection.MailCopyIdHashSet); - //var items = await _mailService.FetchMailsAsync(initializationOptions).ConfigureAwait(false); + var items = await _mailService.FetchMailsAsync(initializationOptions).ConfigureAwait(false); - //var viewModels = PrepareMailViewModels(items); + var viewModels = PrepareMailViewModels(items); - //await ExecuteUIThread(() => { MailCollection.AddRange(viewModels, clearIdCache: false); }); - //await ExecuteUIThread(() => { IsInitializingFolder = false; }); + await ExecuteUIThread(() => { MailCollection.AddEmails(viewModels); }); + await ExecuteUIThread(() => { IsInitializingFolder = false; }); } #endregion @@ -589,7 +580,6 @@ public partial class MailListPageViewModel : MailBaseViewModel, public IEnumerable GetAvailableMailActions(IEnumerable contextMailItems) => _contextMenuItemService.GetMailItemContextMenuActions(contextMailItems.Select(a => a.MailCopy)); - private bool ShouldPreventItemAdd(MailCopy mailItem) { bool condition = mailItem.IsRead @@ -733,13 +723,6 @@ public partial class MailListPageViewModel : MailBaseViewModel, private IEnumerable PrepareMailViewModels(IEnumerable mailItems) { return mailItems.Select(a => new MailItemViewModel(a)); - //foreach (var item in mailItems) - //{ - // if (item is MailCopy singleMailItem) - // yield return new MailItemViewModel(singleMailItem); - // else if (item is ThreadMailItem threadMailItem) - // yield return new ThreadMailItemViewModel(threadMailItem); - //} } [RelayCommand] @@ -784,7 +767,6 @@ public partial class MailListPageViewModel : MailBaseViewModel, // Here items are sorted and filtered. List items = null; - List onlineSearchItems = null; bool isDoingSearch = !string.IsNullOrEmpty(SearchQuery); bool isDoingOnlineSearch = false; @@ -851,8 +833,7 @@ public partial class MailListPageViewModel : MailBaseViewModel, PreferencesService.IsThreadingEnabled, SelectedFolderPivot.IsFocused, SearchQuery, - default, - onlineSearchItems); + MailCollection.MailCopyIdHashSet); items = await _mailService.FetchMailsAsync(initializationOptions, cancellationToken).ConfigureAwait(false); diff --git a/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs b/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs index 2e230a18..f6bfbe12 100644 --- a/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs +++ b/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs @@ -25,7 +25,6 @@ using Wino.Core.Services; using Wino.Mail.ViewModels.Data; using Wino.Mail.ViewModels.Messages; using Wino.Messaging.Client.Mails; -using Wino.Messaging.Server; using Wino.Messaging.UI; using IMailService = Wino.Core.Domain.Interfaces.IMailService; @@ -355,8 +354,8 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel, // Download missing MIME message using SynchronizationManager await SynchronizationManager.Instance.DownloadMimeMessageAsync( - mailItemViewModel.MailCopy, - mailItemViewModel.AssignedAccount.Id); + mailItemViewModel.MailCopy, + mailItemViewModel.MailCopy.AssignedAccount.Id); } catch (OperationCanceledException) { diff --git a/Wino.Mail.WinUI/AppShell.xaml b/Wino.Mail.WinUI/AppShell.xaml index 13f7a210..03c810fb 100644 --- a/Wino.Mail.WinUI/AppShell.xaml +++ b/Wino.Mail.WinUI/AppShell.xaml @@ -3,7 +3,6 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:abstract="using:Wino.Views.Abstract" - xmlns:advanced="using:Wino.Controls.Advanced" xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals" xmlns:animations="using:CommunityToolkit.WinUI.Animations" xmlns:controls="using:Wino.Controls" @@ -356,7 +355,6 @@ diff --git a/Wino.Mail.WinUI/Controls/Advanced/WinoItemsView.cs b/Wino.Mail.WinUI/Controls/Advanced/WinoItemsView.cs index 5b972551..8772925f 100644 --- a/Wino.Mail.WinUI/Controls/Advanced/WinoItemsView.cs +++ b/Wino.Mail.WinUI/Controls/Advanced/WinoItemsView.cs @@ -1,14 +1,46 @@ using System.Collections.Generic; +using System.Windows.Input; +using CommunityToolkit.WinUI; using Microsoft.UI.Xaml.Controls; namespace Wino.Mail.WinUI.Controls.Advanced; public partial class WinoItemsView : ItemsView { + private const string PART_ScrollView = nameof(PART_ScrollView); + + private ScrollView? _internalScrollView; + + [GeneratedDependencyProperty] + public partial ICommand LoadMoreCommand { get; set; } + public IEnumerable? CastedItemsSource => ItemsSource as IEnumerable; public WinoItemsView() { DefaultStyleKey = typeof(ItemsView); } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _internalScrollView = GetTemplateChild("PART_ScrollView") as ScrollView ?? throw new System.Exception("Can't find the ScrollView in WinoItemsView."); + + _internalScrollView.ViewChanged -= InternalScrollViewPositionChanged; + _internalScrollView.ViewChanged += InternalScrollViewPositionChanged; + } + + private void InternalScrollViewPositionChanged(ScrollView sender, object args) + { + if (_internalScrollView == null) return; + + // No need to raise init request if there are no items in the list. + if (ItemsSource == null) return; + + double progress = sender.VerticalOffset / sender.ScrollableHeight; + + // Trigger when scrolled past 90% of total height + if (progress >= 0.9) LoadMoreCommand?.Execute(null); + } } diff --git a/Wino.Mail.WinUI/Controls/WebViewEditorControl.cs b/Wino.Mail.WinUI/Controls/WebViewEditorControl.cs index 89e47fe1..55f91ade 100644 --- a/Wino.Mail.WinUI/Controls/WebViewEditorControl.cs +++ b/Wino.Mail.WinUI/Controls/WebViewEditorControl.cs @@ -12,7 +12,6 @@ using Wino.Core.Domain; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models; using Wino.Core.Domain.Models.Reader; -using Wino.Core.WinUI; using Wino.Core.WinUI.Extensions; using Wino.Mail.WinUI; @@ -140,7 +139,7 @@ public sealed partial class WebViewEditorControl : Control, IDisposable { this.DefaultStyleKey = typeof(WebViewEditorControl); - IsEditorDarkMode = WinoApplication.Current.UnderlyingThemeService.IsUnderlyingThemeDark(); + IsEditorDarkMode = Core.WinUI.WinoApplication.Current.UnderlyingThemeService.IsUnderlyingThemeDark(); } protected override async void OnApplyTemplate() diff --git a/Wino.Mail.WinUI/ShellWindow.xaml b/Wino.Mail.WinUI/ShellWindow.xaml index 858a2253..0eb46c36 100644 --- a/Wino.Mail.WinUI/ShellWindow.xaml +++ b/Wino.Mail.WinUI/ShellWindow.xaml @@ -12,7 +12,7 @@ - + @@ -25,10 +25,10 @@ HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" BackRequested="BackButtonClicked" + Background="Transparent" IsBackButtonVisible="{x:Bind StatePersistanceService.IsBackButtonVisible, Mode=OneWay}" IsPaneToggleButtonVisible="True" PaneToggleRequested="PaneButtonClicked" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + - @@ -177,42 +107,34 @@ Glyph="" /> - - - - - - - - - - + - + @@ -221,7 +143,10 @@ - + @@ -503,15 +428,18 @@ + +