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
-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>