Bunch of changes for ItemsView and threads.

This commit is contained in:
Burak Kaan Köse
2025-10-18 11:45:10 +02:00
parent 522a2da114
commit ad135c5e32
18 changed files with 229 additions and 252 deletions
+27 -27
View File
@@ -15,56 +15,56 @@
<PackageVersion Include="CommunityToolkit.WinUI.Controls.TokenizingTextBox" Version="8.2.250402" />
<PackageVersion Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
<PackageVersion Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" Version="0.1.250926-build.2293" />
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
<PackageVersion Include="CommunityToolkit.Labs.WinUI.DependencyPropertyGenerator" Version="0.1.250926-build.2293" />
<PackageVersion Include="EmailValidation" Version="1.3.0" />
<PackageVersion Include="gravatar-dotnet" Version="0.1.3" />
<PackageVersion Include="HtmlAgilityPack" Version="1.12.0" />
<PackageVersion Include="HtmlAgilityPack" Version="1.12.4" />
<PackageVersion Include="Ical.Net" Version="4.3.1" />
<PackageVersion Include="IsExternalInit" Version="1.0.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.9" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.4" />
<PackageVersion Include="Microsoft.Graph" Version="5.75.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.10" />
<PackageVersion Include="Microsoft.Graph" Version="5.94.0" />
<PackageVersion Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageVersion Include="Microsoft.Identity.Client" Version="4.77.1" />
<PackageVersion Include="Microsoft.Identity.Client.Broker" Version="4.70.1" />
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.70.1" />
<PackageVersion Include="Microsoft.Identity.Client.Broker" Version="4.77.1" />
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.77.1" />
<PackageVersion Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.2.14" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.0" />
<PackageVersion Include="MimeKit" Version="4.11.0" />
<PackageVersion Include="MimeKit" Version="4.14.0" />
<PackageVersion Include="morelinq" Version="4.4.0" />
<PackageVersion Include="Nito.AsyncEx" Version="5.1.2" />
<PackageVersion Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
<PackageVersion Include="NodaTime" Version="3.2.2" />
<PackageVersion Include="Sentry.Serilog" Version="5.15.1" />
<PackageVersion Include="Serilog" Version="4.2.0" />
<PackageVersion Include="Sentry.Serilog" Version="5.16.1" />
<PackageVersion Include="Serilog" Version="4.3.0" />
<PackageVersion Include="Serilog.Exceptions" Version="8.4.0" />
<PackageVersion Include="Serilog.Sinks.Debug" Version="3.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageVersion Include="Serilog.Sinks.ApplicationInsights" Version="4.0.0" />
<PackageVersion Include="SkiaSharp" Version="3.116.1" />
<PackageVersion Include="SkiaSharp" Version="3.119.1" />
<PackageVersion Include="sqlite-net-pcl" Version="1.9.172" />
<PackageVersion Include="SqlKata" Version="4.0.1" />
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.4" />
<PackageVersion Include="System.Text.Json" Version="9.0.4" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.10" />
<PackageVersion Include="System.Text.Json" Version="9.0.10" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.3.0" />
<PackageVersion Include="H.NotifyIcon.WinUI" Version="2.3.0" />
<PackageVersion Include="H.NotifyIcon.WinUI" Version="2.3.1" />
<PackageVersion Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
<PackageVersion Include="Google.Apis.Auth" Version="1.69.0" />
<PackageVersion Include="Google.Apis.Calendar.v3" Version="1.69.0.3667" />
<PackageVersion Include="Google.Apis.Gmail.v1" Version="1.68.0.3427" />
<PackageVersion Include="Google.Apis.PeopleService.v1" Version="1.68.0.3359" />
<PackageVersion Include="Google.Apis.Auth" Version="1.72.0" />
<PackageVersion Include="Google.Apis.Calendar.v3" Version="1.69.0.3746" />
<PackageVersion Include="Google.Apis.Gmail.v1" Version="1.70.0.3833" />
<PackageVersion Include="Google.Apis.PeopleService.v1" Version="1.69.0.3785" />
<PackageVersion Include="HtmlKit" Version="1.2.0" />
<PackageVersion Include="MailKit" Version="4.11.0" />
<PackageVersion Include="MailKit" Version="4.14.1" />
<PackageVersion Include="TimePeriodLibrary.NET" Version="2.1.6" />
<PackageVersion Include="System.Reactive" Version="6.0.1" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.4" />
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.4" />
<PackageVersion Include="System.Reactive" Version="6.1.0" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.10" />
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.10" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6584" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.250916003" />
<PackageVersion Include="WinUIEx" Version="2.8.0" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.0.250930001-experimental1" />
<PackageVersion Include="WinUIEx" Version="2.9.0" />
</ItemGroup>
</Project>
</Project>
@@ -12,5 +12,7 @@ public record MailListInitializationOptions(IEnumerable<IMailItemFolder> Folders
bool CreateThreads,
bool? IsFocusedOnly,
string SearchQuery,
IEnumerable<Guid> ExistingUniqueIds,
List<MailCopy> PreFetchMailCopies = null);
HashSet<Guid> ExistingUniqueIds = null,
List<MailCopy> PreFetchMailCopies = null,
int Skip = 0,
int Take = 0);
+1 -2
View File
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<Platforms>x86;x64;arm64</Platforms>
@@ -57,7 +57,6 @@
<PackageReference Include="MimeKit" />
<PackageReference Include="MailKit" />
<PackageReference Include="sqlite-net-pcl" />
<PackageReference Include="System.Text.Json" />
<PackageReference Include="TimePeriodLibrary.NET" />
</ItemGroup>
<ItemGroup>
+1
View File
@@ -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)
@@ -39,6 +39,7 @@ public partial class GroupedEmailCollection : ObservableObject, IRecipient<Prope
private readonly Dictionary<string, int> _groupHeaderIndexCache = [];
private readonly Dictionary<string, List<object>> _groupItems = [];
private readonly Dictionary<string, ThreadMailItemViewModel> _threadExpanders = [];
private readonly HashSet<Guid> _mailCopyIdHashSet = [];
private bool _disposed;
private bool _isUpdating;
@@ -78,6 +79,11 @@ public partial class GroupedEmailCollection : ObservableObject, IRecipient<Prope
/// </summary>
public int TotalUnreadCount => _sourceItems.Count(e => e.MailCopy?.IsRead == false);
/// <summary>
/// HashSet containing unique IDs of all mail copies in the collection for pagination tracking
/// </summary>
public HashSet<Guid> MailCopyIdHashSet => _mailCopyIdHashSet;
/// <summary>
/// Gets all email items across all groups as a flat collection
/// </summary>
@@ -227,6 +233,9 @@ public partial class GroupedEmailCollection : ObservableObject, IRecipient<Prope
if (email?.MailCopy == null)
return;
// Add to unique ID tracking
_mailCopyIdHashSet.Add(email.MailCopy.UniqueId);
_isUpdating = true;
try
{
@@ -306,6 +315,9 @@ public partial class GroupedEmailCollection : ObservableObject, IRecipient<Prope
if (email?.MailCopy == null)
return;
// Remove from unique ID tracking
_mailCopyIdHashSet.Remove(email.MailCopy.UniqueId);
_isUpdating = true;
try
{
@@ -386,6 +398,12 @@ public partial class GroupedEmailCollection : ObservableObject, IRecipient<Prope
_isUpdating = true;
try
{
// Add to unique ID tracking
foreach (var email in emailList)
{
_mailCopyIdHashSet.Add(email.MailCopy.UniqueId);
}
// For bulk loading, add to source and refresh
foreach (var email in emailList)
{
@@ -430,6 +448,7 @@ public partial class GroupedEmailCollection : ObservableObject, IRecipient<Prope
_groupHeaderIndexCache.Clear();
_groupItems.Clear();
_threadExpanders.Clear();
_mailCopyIdHashSet.Clear();
OnPropertyChanged(nameof(TotalCount));
OnPropertyChanged(nameof(TotalUnreadCount));
+3 -4
View File
@@ -13,14 +13,13 @@ using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Exceptions;
using Wino.Core.Services;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Extensions;
using Wino.Core.Services;
using Wino.Mail.ViewModels.Data;
using Wino.Messaging.Client.Mails;
using Wino.Messaging.Server;
namespace Wino.Mail.ViewModels;
@@ -411,8 +410,8 @@ public partial class ComposePageViewModel : MailBaseViewModel
// Download missing MIME message using SynchronizationManager
await SynchronizationManager.Instance.DownloadMimeMessageAsync(
CurrentMailDraftItem.MailCopy,
CurrentMailDraftItem.AssignedAccount.Id);
CurrentMailDraftItem.MailCopy,
CurrentMailDraftItem.MailCopy.AssignedAccount.Id);
goto retry;
}
@@ -53,16 +53,13 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IDisposable
/// </summary>
public IReadOnlyList<MailItemViewModel> 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));
}
+18 -37
View File
@@ -60,14 +60,6 @@ public partial class MailListPageViewModel : MailBaseViewModel,
private IObservable<System.Reactive.EventPattern<NotifyCollectionChangedEventArgs>> selectionChangedObservable = null;
public GroupedEmailCollection MailCollection { get; set; } = new GroupedEmailCollection();
//public IEnumerable<MailItemViewModel> SelectedItems
//{
// get
// {
// }
//}
public ObservableCollection<MailItemViewModel> SelectedItems { get; set; } = [];
public ObservableCollection<FolderPivotViewModel> PivotFolders { get; set; } = [];
public ObservableCollection<MailOperationMenuItem> 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<MailOperationMenuItem> GetAvailableMailActions(IEnumerable<MailItemViewModel> 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<MailItemViewModel> PrepareMailViewModels(IEnumerable<MailCopy> 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<MailCopy> items = null;
List<MailCopy> 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);
@@ -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)
{
-2
View File
@@ -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 @@
<Grid
x:Name="RootGrid"
Padding="0"
Background="{ThemeResource WinoApplicationBackgroundColor}"
ColumnSpacing="0"
RowSpacing="0">
@@ -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<object>? CastedItemsSource => ItemsSource as IEnumerable<object>;
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);
}
}
@@ -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()
+2 -2
View File
@@ -12,7 +12,7 @@
<!-- SystemBackdrop will be set by NewThemeService -->
<Grid>
<Grid Background="{ThemeResource WinoApplicationBackgroundColor}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
@@ -25,10 +25,10 @@
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
BackRequested="BackButtonClicked"
Background="Transparent"
IsBackButtonVisible="{x:Bind StatePersistanceService.IsBackButtonVisible, Mode=OneWay}"
IsPaneToggleButtonVisible="True"
PaneToggleRequested="PaneButtonClicked" />
<Frame
x:Name="MainShellFrame"
Grid.Row="1"
+48 -120
View File
@@ -67,104 +67,34 @@
<animations:OpacityAnimation To="0.0" Duration="0:0:1" />
</animations:Implicit.HideAnimations>
<Grid Height="80">
<!-- Thread background -->
<Grid
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{ThemeResource CardStrokeColorDefaultBrush}"
Visibility="{x:Bind IsDisplayedInThread, Mode=OneWay}" />
<Grid Padding="8,4,12,4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Left: PersonPicture -->
<PersonPicture
Grid.Column="0"
Width="40"
Height="40"
Margin="0,0,12,0"
VerticalAlignment="Center"
DisplayName="{x:Bind FromName, Mode=OneWay}" />
<!-- Center: Content -->
<StackPanel
Grid.Column="1"
VerticalAlignment="Center"
Spacing="2">
<!-- Subject -->
<TextBlock
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightByReadState(IsRead), Mode=OneWay}"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
MaxLines="1"
Text="{x:Bind Subject, Mode=OneWay}"
TextTrimming="CharacterEllipsis" />
<!-- Sender Name -->
<TextBlock
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightByReadState(IsRead), Mode=OneWay}"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
MaxLines="1"
Text="{x:Bind FromName, Mode=OneWay}"
TextTrimming="CharacterEllipsis" />
<!-- Preview Text -->
<TextBlock
Foreground="{ThemeResource TextFillColorTertiaryBrush}"
MaxLines="1"
Text="{x:Bind PreviewText, Mode=OneWay}"
TextTrimming="CharacterEllipsis" />
</StackPanel>
<!-- Right: Indicators -->
<StackPanel
Grid.Column="2"
Margin="12,0,0,0"
VerticalAlignment="Center"
Spacing="4">
<!-- Unread Indicator (show when IsRead is false) -->
<Ellipse
Width="8"
Height="8"
Fill="{ThemeResource AccentTextFillColorPrimaryBrush}"
Visibility="{x:Bind helpers:XamlHelpers.ReverseBoolToVisibilityConverter(IsRead), Mode=OneWay}" />
<!-- Flagged Indicator -->
<FontIcon
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="12"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="&#xE129;"
Visibility="{x:Bind IsFlagged, Mode=OneWay}" />
<!-- Attachment Indicator -->
<FontIcon
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xE723;"
Visibility="{x:Bind HasAttachments, Mode=OneWay}" />
</StackPanel>
</Grid>
</Grid>
<controls:MailItemDisplayInformationControl
Margin="{x:Bind helpers:XamlHelpers.GetMailItemControlMargin(IsDisplayedInThread), Mode=OneWay}"
x:DefaultBindMode="OneWay"
CenterHoverAction="{Binding ElementName=root, Path=ViewModel.PreferencesService.CenterHoverAction, Mode=OneWay}"
ContextRequested="MailItemContextRequested"
DisplayMode="{Binding ElementName=root, Path=ViewModel.PreferencesService.MailItemDisplayMode, Mode=OneWay}"
HoverActionExecutedCommand="{Binding ElementName=root, Path=ViewModel.ExecuteHoverActionCommand}"
IsAvatarVisible="{Binding ElementName=root, Path=ViewModel.PreferencesService.IsShowSenderPicturesEnabled, Mode=OneWay}"
IsHoverActionsEnabled="{Binding ElementName=root, Path=ViewModel.PreferencesService.IsHoverActionsEnabled, Mode=OneWay}"
IsThumbnailUpdated="{x:Bind ThumbnailUpdatedEvent, Mode=OneWay}"
LeftHoverAction="{Binding ElementName=root, Path=ViewModel.PreferencesService.LeftHoverAction, Mode=OneWay}"
MailItem="{x:Bind MailCopy, Mode=OneWay}"
Prefer24HourTimeFormat="{Binding ElementName=root, Path=ViewModel.PreferencesService.Prefer24HourTimeFormat, Mode=OneWay}"
RightHoverAction="{Binding ElementName=root, Path=ViewModel.PreferencesService.RightHoverAction, Mode=OneWay}"
ShowPreviewText="{Binding ElementName=root, Path=ViewModel.PreferencesService.IsShowPreviewEnabled, Mode=OneWay}" />
</ItemContainer>
</DataTemplate>
<DataTemplate x:Key="ThreadExpanderTemplate" x:DataType="viewModelData:ThreadMailItemViewModel">
<ItemContainer Tag="{x:Bind}" Tapped="ThreadContainerTapped">
<Grid
Padding="4,12,0,12"
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
ColumnSpacing="8">
<ItemContainer
CanUserSelect="UserCannotSelect"
RightTapped="ThreadContainerRightTapped"
Tag="{x:Bind}"
Tapped="ThreadContainerTapped">
<Grid Padding="4,0,0,0" ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Expansion indicator -->
@@ -177,42 +107,34 @@
Glyph="&#xE76C;" />
</Viewbox>
<!-- Thread content -->
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock
FontWeight="SemiBold"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
Text="{x:Bind Subject, Mode=OneWay}"
TextTrimming="CharacterEllipsis" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{ThemeResource CaptionTextBlockStyle}"
Text="{x:Bind LatestEmailDate, Mode=OneWay}"
TextTrimming="CharacterEllipsis" />
</StackPanel>
<!-- Email count badge -->
<Border
Grid.Column="2"
Margin="8,0,12,0"
Padding="6,2"
VerticalAlignment="Center"
Background="{ThemeResource AccentTextFillColorPrimaryBrush}"
CornerRadius="8">
<TextBlock
Foreground="White"
Style="{ThemeResource CaptionTextBlockStyle}"
Text="{x:Bind EmailCount, Mode=OneWay}" />
</Border>
<controls:MailItemDisplayInformationControl
Grid.Column="1"
x:DefaultBindMode="OneWay"
CenterHoverAction="{Binding ElementName=root, Path=ViewModel.PreferencesService.CenterHoverAction, Mode=OneWay}"
ContextRequested="MailItemContextRequested"
DisplayMode="{Binding ElementName=root, Path=ViewModel.PreferencesService.MailItemDisplayMode, Mode=OneWay}"
HoverActionExecutedCommand="{Binding ElementName=root, Path=ViewModel.ExecuteHoverActionCommand}"
IsAvatarVisible="{Binding ElementName=root, Path=ViewModel.PreferencesService.IsShowSenderPicturesEnabled, Mode=OneWay}"
IsHoverActionsEnabled="{Binding ElementName=root, Path=ViewModel.PreferencesService.IsHoverActionsEnabled, Mode=OneWay}"
IsThumbnailUpdated="{x:Bind LatestMailViewModel.ThumbnailUpdatedEvent, Mode=OneWay}"
LeftHoverAction="{Binding ElementName=root, Path=ViewModel.PreferencesService.LeftHoverAction, Mode=OneWay}"
MailItem="{x:Bind LatestMailViewModel.MailCopy, Mode=OneWay}"
Prefer24HourTimeFormat="{Binding ElementName=root, Path=ViewModel.PreferencesService.Prefer24HourTimeFormat, Mode=OneWay}"
RightHoverAction="{Binding ElementName=root, Path=ViewModel.PreferencesService.RightHoverAction, Mode=OneWay}"
ShowPreviewText="{Binding ElementName=root, Path=ViewModel.PreferencesService.IsShowPreviewEnabled, Mode=OneWay}" />
</Grid>
</ItemContainer>
</DataTemplate>
<DataTemplate x:Key="DateGroupHeaderTemplate" x:DataType="data:DateGroupHeader">
<ItemContainer IsHitTestVisible="False">
<ItemContainer
CanUserSelect="UserCannotSelect"
IsEnabled="False"
IsHitTestVisible="False">
<Grid Padding="12,8" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}">
<TextBlock
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
Style="{ThemeResource CaptionTextBlockStyle}"
Text="{x:Bind DisplayName, Mode=OneWay}" />
@@ -221,7 +143,10 @@
</DataTemplate>
<DataTemplate x:Key="SenderGroupHeaderTemplate" x:DataType="data:SenderGroupHeader">
<ItemContainer IsHitTestVisible="False">
<ItemContainer
CanUserSelect="UserCannotSelect"
IsEnabled="False"
IsHitTestVisible="False">
<Grid Padding="12,8" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
@@ -503,15 +428,18 @@
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<advanced:WinoItemsView
x:Name="MailListView"
Margin="4"
ItemTemplate="{StaticResource MailItemContainerSelector}"
ItemsSource="{x:Bind ViewModel.MailCollection.Items, Mode=OneTime}"
Layout="{StaticResource DefaultItemsViewLayout}"
LoadMoreCommand="{x:Bind ViewModel.LoadMoreItemsCommand}"
SelectionChanged="ListSelectionChanged"
SelectionMode="Extended" />
<!-- Try online search panel. -->
<Grid Grid.Row="1" Visibility="{x:Bind ViewModel.IsOnlineSearchButtonVisible, Mode=OneWay}">
<Button
+43 -36
View File
@@ -13,6 +13,7 @@ using Microsoft.UI.Xaml.Media.Animation;
using Microsoft.UI.Xaml.Navigation;
using MoreLinq;
using Windows.Foundation;
using Wino.Controls;
using Wino.Core.Domain;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
@@ -74,7 +75,7 @@ public sealed partial class MailListPage : MailListPageAbstract,
// Dispose all WinoListView items.
MailListView.Dispose();
// MailListView.Dispose();
this.Bindings.StopTracking();
@@ -162,31 +163,24 @@ public sealed partial class MailListPage : MailListPageAbstract,
// Context is requested from a single mail point, but we might have multiple selected items.
// This menu should be calculated based on all selected items by providers.
//if (sender is MailItemDisplayInformationControl control && args.TryGetPosition(sender, out Point p))
//{
// await FocusManager.TryFocusAsync(control, FocusState.Keyboard);
if (sender is MailItemDisplayInformationControl control && args.TryGetPosition(sender, out Point p))
{
if (control.DataContext is MailItemViewModel clickedMailItemContext)
{
var targetItems = ViewModel.MailCollection.AllItems.Where(a => a.IsSelected);
var availableActions = ViewModel.GetAvailableMailActions(targetItems);
// if (control.DataContext is IMailItem clickedMailItemContext)
// {
// var targetItems = ViewModel.GetTargetMailItemViewModels(clickedMailItemContext);
// var availableActions = ViewModel.GetAvailableMailActions(targetItems);
if (!availableActions?.Any() ?? false) return;
// if (!availableActions?.Any() ?? false) return;
// var t = targetItems.ElementAt(0);
var clickedOperation = await GetMailOperationFromFlyoutAsync(availableActions, control, p.X, p.Y);
// ViewModel.ChangeCustomFocusedState(targetItems, true);
if (clickedOperation == null) return;
// var clickedOperation = await GetMailOperationFromFlyoutAsync(availableActions, control, p.X, p.Y);
var prepRequest = new MailOperationPreperationRequest(clickedOperation.Operation, targetItems.Select(a => a.MailCopy));
// ViewModel.ChangeCustomFocusedState(targetItems, false);
// if (clickedOperation == null) return;
// var prepRequest = new MailOperationPreperationRequest(clickedOperation.Operation, targetItems.Select(a => a.MailCopy));
// await ViewModel.ExecuteMailOperationAsync(prepRequest);
// }
//}
await ViewModel.ExecuteMailOperationAsync(prepRequest);
}
}
}
private async Task<MailOperationMenuItem> GetMailOperationFromFlyoutAsync(IEnumerable<MailOperationMenuItem> availableActions,
@@ -507,22 +501,7 @@ public sealed partial class MailListPage : MailListPageAbstract,
private void DeleteAllInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
=> ViewModel.ExecuteMailOperationCommand.Execute(MailOperation.SoftDelete);
private void ThreadContainerTapped(object sender, TappedRoutedEventArgs e)
{
if (sender is ItemContainer container && container.Tag is ThreadMailItemViewModel expander)
{
// Toggle expansion state
expander.IsThreadExpanded = !expander.IsThreadExpanded;
// Find the expander icon and animate its rotation using Composition APIs
var expanderIcon = WinoVisualTreeHelper.GetChildObject<FontIcon>(container, "ExpanderIcon");
if (expanderIcon != null)
{
var targetAngle = expander.IsThreadExpanded ? 90f : 0f;
AnimateRotationWithComposition(expanderIcon, targetAngle);
}
}
}
/// <summary>
/// Animates the rotation using high-performance Composition APIs
@@ -561,4 +540,32 @@ public sealed partial class MailListPage : MailListPageAbstract,
UpdateSelectAllButtonStatus();
UpdateAdaptiveness();
}
private void ThreadContainerRightTapped(object sender, RightTappedRoutedEventArgs e)
{
if (sender is ItemContainer container && container.Tag is ThreadMailItemViewModel expander)
{
expander.IsThreadExpanded = !expander.IsThreadExpanded;
// Select all.
ViewModel.MailCollection.AllItems.Where(a => expander.ThreadEmails.Contains(a)).ForEach(a => a.IsSelected = true);
}
}
private void ThreadContainerTapped(object sender, TappedRoutedEventArgs e)
{
if (sender is ItemContainer container && container.Tag is ThreadMailItemViewModel expander)
{
// Toggle expansion state
expander.IsThreadExpanded = !expander.IsThreadExpanded;
// Find the expander icon and animate its rotation using Composition APIs
var expanderIcon = WinoVisualTreeHelper.GetChildObject<FontIcon>(container, "ExpanderIcon");
if (expanderIcon != null)
{
var targetAngle = expander.IsThreadExpanded ? 90f : 0f;
AnimateRotationWithComposition(expanderIcon, targetAngle);
}
}
}
}
@@ -108,6 +108,7 @@
<TextBlock
x:Name="ElementThemeSelectionDisabledTextBlock"
Margin="4,0,0,0"
x:Load="{x:Bind ViewModel.CanSelectElementTheme, Mode=OneWay, Converter={StaticResource ReverseBooleanConverter}}"
Foreground="{ThemeResource InfoBarWarningSeverityIconBackground}"
Text="{x:Bind domain:Translator.SettingsElementThemeSelectionDisabled}" />
@@ -149,7 +150,10 @@
</controls:SettingsExpander>
<!-- Backdrop Selection -->
<controls:SettingsCard Description="Choose the backdrop effect for your app window" Header="Window Backdrop">
<controls:SettingsCard
Description="Choose the backdrop effect for your app window"
Header="Window Backdrop"
IsEnabled="{x:Bind ViewModel.CanSelectElementTheme, Mode=OneWay}">
<controls:SettingsCard.HeaderIcon>
<PathIcon Data="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z" />
</controls:SettingsCard.HeaderIcon>
+14 -9
View File
@@ -28,6 +28,7 @@ public class MailService : BaseDatabaseService, IMailService
private readonly IContactService _contactService;
private readonly IAccountService _accountService;
private readonly ISignatureService _signatureService;
private readonly IMimeFileService _mimeFileService;
private readonly IPreferencesService _preferencesService;
@@ -192,15 +193,23 @@ public class MailService : BaseDatabaseService, IMailService
.OrWhereContains("MailCopy.FromName", options.SearchQuery)
.OrWhereContains("MailCopy.FromAddress", options.SearchQuery));
// Support pagination by excluding already fetched items
if (options.ExistingUniqueIds?.Any() ?? false)
{
query.WhereNotIn("MailCopy.UniqueId", options.ExistingUniqueIds);
}
//if (options.Skip > 0)
//{
// query.Skip(options.Skip);
//}
// Support skip for pagination
if (options.Skip > 0)
{
query.Skip(options.Skip);
}
// Support custom take count for pagination
if (options.Take > 0)
{
query.Take(options.Take);
}
return query.GetRawQuery();
}
@@ -218,7 +227,6 @@ public class MailService : BaseDatabaseService, IMailService
{
// If not just do the query.
var query = BuildMailFetchQuery(options);
mails = await Connection.QueryAsync<MailCopy>(query);
}
@@ -239,9 +247,6 @@ public class MailService : BaseDatabaseService, IMailService
cancellationToken.ThrowIfCancellationRequested();
// Threading is disabled. Just return everything as it is.
// mails.Sort(options.SortingOptionType == SortingOptionType.ReceiveDate ? new DateComparer() : new NameComparer());
return [.. mails];
}
@@ -1005,7 +1010,7 @@ public class MailService : BaseDatabaseService, IMailService
public async Task<List<MailCopy>> GetExistingMailsAsync(Guid folderId, IEnumerable<MailKit.UniqueId> uniqueIds)
{
var localMailIds = uniqueIds.Where(a => a != null).Select(a => MailkitClientExtensions.CreateUid(folderId, a.Id)).ToArray();
var localMailIds = uniqueIds.Select(a => MailkitClientExtensions.CreateUid(folderId, a.Id)).ToArray();
var query = new Query(nameof(MailCopy))
.WhereIn("Id", localMailIds)
+5
View File
@@ -14,9 +14,14 @@
<!-- key value for <packageSource> should match key values from <packageSources> element -->
<packageSource key="nuget">
<package pattern="*" />
<package pattern="CommunityToolkit.Common" />
<package pattern="Microsoft.*" />
<package pattern="Newtonsoft.Json" />
<package pattern="CommunityToolkit.WinUI.Extensions" />
</packageSource>
<packageSource key="labsFeed">
<package pattern="CommunityToolkit.Labs.*" />
<package pattern="CommunityToolkit.WinUI.Extensions" />
</packageSource>
</packageSourceMapping>
</configuration>