New WinoListView implementation with multiple selections.

This commit is contained in:
Burak Kaan Köse
2025-10-26 14:53:22 +01:00
parent d4c8ae6cb7
commit 79d5b6ed40
22 changed files with 748 additions and 480 deletions
+2
View File
@@ -12,9 +12,11 @@
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<uwp:CoreGeneric />
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.Segmented/Segmented/Segmented.xaml" />
<ResourceDictionary Source="Controls/ListView/WinoListViewStyles.xaml" />
<ResourceDictionary Source="Styles/ItemContainerStyles.xaml" />
<ResourceDictionary Source="Styles/ImagePreviewControl.xaml" />
<ResourceDictionary Source="Styles/WebViewEditorControl.xaml" />
<ResourceDictionary Source="Controls/ListView/WinoListViewStyles.xaml" />
<styles:WinoExpanderStyle />
<ResourceDictionary Source="/Wino.Core.WinUI/AppThemes/Default.xaml" />
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Windows.Input;
using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml.Controls;
@@ -6,6 +7,7 @@ using Wino.Mail.ViewModels.Data;
namespace Wino.Mail.WinUI.Controls.Advanced;
[Obsolete("ItemsView sucks. Hard to deal with virtualization issues. Use ListView. This control is here to wise up anyone who tries to use it.")]
public partial class WinoItemsView : ItemsView
{
private const string PART_ScrollView = nameof(PART_ScrollView);
@@ -0,0 +1,49 @@
using Microsoft.UI.Xaml;
using Wino.Mail.ViewModels.Data;
namespace Wino.Mail.WinUI.Controls.ListView;
public partial class WinoListView : Microsoft.UI.Xaml.Controls.ListView
{
public bool IsAllSelected => Items.Count == SelectedItems.Count;
protected override DependencyObject GetContainerForItemOverride() => new WinoListViewItem();
public bool SelectMailItemContainer(MailItemViewModel mailItemViewModel)
{
WinoListViewItem? itemContainer = null;
foreach (var item in Items)
{
if (item is MailItemViewModel mailItem && mailItem.Id == mailItemViewModel.Id)
{
itemContainer = ContainerFromItem(mailItemViewModel) as WinoListViewItem;
break;
}
else if (item is ThreadMailItemViewModel threadMailItemViewModel && threadMailItemViewModel.HasUniqueId(mailItemViewModel.MailCopy.UniqueId))
{
itemContainer = ContainerFromItem(threadMailItemViewModel) as WinoListViewItem;
// Try to get the inner WinoListView.
if (itemContainer != null)
{
itemContainer.IsExpanded = true;
var innerListViewControl = itemContainer.GetWinoListViewControl();
if (innerListViewControl != null)
{
itemContainer = innerListViewControl.ContainerFromItem(mailItemViewModel) as WinoListViewItem;
}
}
break;
}
}
itemContainer?.IsSelected = true;
return itemContainer != null;
}
}
@@ -0,0 +1,103 @@
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Wino.Mail.ViewModels.Data;
using Wino.Messaging.Client.Mails;
namespace Wino.Mail.WinUI.Controls.ListView;
public partial class WinoListViewItem : ListViewItem
{
public bool IsExpanded
{
get { return (bool)GetValue(IsExpandedProperty); }
set { SetValue(IsExpandedProperty, value); }
}
public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register(nameof(IsExpanded), typeof(bool), typeof(WinoListViewItem), new PropertyMetadata(false, OnIsExpandedChanged));
public WinoListViewItem()
{
DefaultStyleKey = typeof(WinoListViewItem);
RegisterPropertyChangedCallback(IsSelectedProperty, OnIsSelectedChanged);
}
private static void OnIsExpandedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is WinoListViewItem item)
{
// Handle expansion state change if needed
}
}
protected override void OnContentChanged(object oldContent, object newContent)
{
base.OnContentChanged(oldContent, newContent);
if (oldContent is IMailListItem oldMailItem)
{
UnregisterSelectionCallback(oldMailItem);
}
if (newContent is IMailListItem newMailItem)
{
IsSelected = newMailItem.IsSelected;
RegisterSelectionCallback(newMailItem);
}
}
private void UnregisterSelectionCallback(IMailListItem mailItem)
{
mailItem.PropertyChanged -= MailPropChanged;
}
private void RegisterSelectionCallback(IMailListItem mailItem)
{
mailItem.PropertyChanged += MailPropChanged;
}
// From model
private void MailPropChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (sender is not IMailListItem mailItem) return;
if (e.PropertyName == nameof(IMailListItem.IsSelected)) ApplySelectionForContainer(mailItem);
}
// From container.
private void OnIsSelectedChanged(DependencyObject sender, DependencyProperty dp)
{
if (Content is IMailListItem mailItem)
{
ApplySelectionForModel(mailItem);
}
}
private void ApplySelectionForModel(IMailListItem mailItem)
{
if (mailItem.IsSelected != IsSelected)
{
mailItem.IsSelected = IsSelected;
WeakReferenceMessenger.Default.Send(new SelectedItemsChangedMessage());
}
}
private void ApplySelectionForContainer(IMailListItem mailItem)
{
if (IsSelected != mailItem.IsSelected)
{
IsSelected = mailItem.IsSelected;
}
}
public WinoListView? GetWinoListViewControl()
{
var expander = GetTemplateChild("ExpanderPart") as Expander;
if (expander?.Content is ContentPresenter presenter) return VisualTreeHelper.GetChild(presenter, 0) as WinoListView;
return null;
}
}
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Wino.Mail.WinUI.Controls.ListView">
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<ResourceDictionary>
<!-- Thread Mail ListViewItem Style -->
<Style x:Key="DefaultThreadListViewItemStyle" TargetType="local:WinoListViewItem">
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="Background" Value="{ThemeResource ListViewItemBackground}" />
<Setter Property="Foreground" Value="{ThemeResource ListViewItemForeground}" />
<Setter Property="TabNavigation" Value="Local" />
<Setter Property="IsHoldingEnabled" Value="True" />
<Setter Property="Padding" Value="16,0,12,0" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="MinWidth" Value="{ThemeResource ListViewItemMinWidth}" />
<Setter Property="MinHeight" Value="{ThemeResource ListViewItemMinHeight}" />
<Setter Property="AllowDrop" Value="False" />
<Setter Property="UseSystemFocusVisuals" Value="True" />
<Setter Property="FocusVisualMargin" Value="1" />
<Setter Property="FocusVisualPrimaryBrush" Value="{ThemeResource ListViewItemFocusVisualPrimaryBrush}" />
<Setter Property="FocusVisualPrimaryThickness" Value="2" />
<Setter Property="FocusVisualSecondaryBrush" Value="{ThemeResource ListViewItemFocusVisualSecondaryBrush}" />
<Setter Property="FocusVisualSecondaryThickness" Value="1" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:WinoListViewItem">
<Expander Header="Thread" IsExpanded="{TemplateBinding IsExpanded}">
<Expander.Content>
<!-- Expandable Content -->
<ContentPresenter
x:Name="ThreadContent"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}" />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Default Single Mail List View Item Style. -->
<Style
x:Key="DefaultMailListViewItemStyle"
BasedOn="{StaticResource DefaultListViewItemStyle}"
TargetType="local:WinoListViewItem" />
<local:WinoMailItemContainerStyleSelector
x:Name="WinoMailItemContainerStyleSelector"
MailItemStyle="{StaticResource DefaultMailListViewItemStyle}"
ThreadStyle="{StaticResource DefaultThreadListViewItemStyle}" />
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
@@ -0,0 +1,19 @@
using System;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Wino.Mail.ViewModels.Data;
namespace Wino.Mail.WinUI.Controls.ListView;
public partial class WinoMailItemContainerStyleSelector : StyleSelector
{
public Style? ThreadStyle { get; set; }
public Style? MailItemStyle { get; set; }
protected override Style SelectStyleCore(object item, DependencyObject container)
{
if (item is MailItemViewModel) return MailItemStyle ?? throw new Exception($"Missing style for {nameof(MailItemViewModel)}");
if (item is ThreadMailItemViewModel) return ThreadStyle ?? throw new Exception($"Missing style for {nameof(ThreadMailItemViewModel)}");
return null;
}
}
@@ -0,0 +1,22 @@
using System;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Wino.Mail.ViewModels.Data;
namespace Wino.Mail.WinUI.Controls.ListView;
public partial class WinoMailItemTemplateSelector : DataTemplateSelector
{
public DataTemplate? SingleMailItemTemplate { get; set; }
public DataTemplate? ThreadMailItemTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
if (item is MailItemViewModel)
return SingleMailItemTemplate ?? throw new Exception($"Missing template for single mail items.");
else if (item is ThreadMailItemViewModel)
return ThreadMailItemTemplate ?? throw new Exception($"Missing template for thread mail items.");
return base.SelectTemplateCore(item, container);
}
}
@@ -1,18 +0,0 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Wino.Mail.ViewModels.Data;
namespace Wino.Selectors;
public partial class MailItemContainerStyleSelector : StyleSelector
{
public Style Thread { get; set; }
protected override Style SelectStyleCore(object item, DependencyObject container)
{
if (item is ThreadMailItemViewModel)
return Thread;
else
return base.SelectStyleCore(item, container);
}
}
@@ -1,21 +0,0 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Wino.Mail.ViewModels.Data;
namespace Wino.Selectors;
public partial class MailItemDisplaySelector : DataTemplateSelector
{
public DataTemplate SingleMailItemTemplate { get; set; }
public DataTemplate ThreadMailItemTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
if (item is MailItemViewModel)
return SingleMailItemTemplate;
else if (item is ThreadMailItemViewModel)
return ThreadMailItemTemplate;
return base.SelectTemplateCore(item, container);
}
}
+8 -2
View File
@@ -234,8 +234,14 @@ public sealed partial class ComposePage : ComposePageAbstract,
FocusManager.GotFocus += GlobalFocusManagerGotFocus;
var anim = ConnectedAnimationService.GetForCurrentView().GetAnimation("WebViewConnectedAnimation");
anim?.TryStart(GetWebView());
var webView = GetWebView();
if (webView != null)
{
var anim = ConnectedAnimationService.GetForCurrentView().GetAnimation("WebViewConnectedAnimation");
anim?.TryStart(webView);
}
_disposables.Add(GetSuggestionBoxDisposable(ToBox));
_disposables.Add(GetSuggestionBoxDisposable(CCBox));
+76 -137
View File
@@ -17,6 +17,7 @@
xmlns:helpers="using:Wino.Helpers"
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:listview="using:Wino.Mail.WinUI.Controls.ListView"
xmlns:local="using:Wino.Behaviors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:menuflyouts="using:Wino.MenuFlyouts"
@@ -49,147 +50,55 @@
<SolidColorBrush x:Key="ButtonBackgroundDisabled">Transparent</SolidColorBrush>
<!--
Virtualization is disabled because it messes up the selected items.
When item is unrealized, it's selection state is lost.
I don't want to intercept ItemContainer for it.
-->
<StackLayout
x:Key="DefaultItemsViewLayout"
IsVirtualizationEnabled="False"
Orientation="Vertical"
Spacing="6" />
<!-- Templates -->
<DataTemplate x:Key="EmailTemplate" x:DataType="viewModelData:MailItemViewModel">
<ItemContainer
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
CanUserInvoke="UserCanInvoke"
IsSelected="{x:Bind IsSelected, Mode=TwoWay}">
<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"
DataContext="{x:Bind}"
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>
<!-- Single Mail Item Template -->
<DataTemplate x:Key="SingleMailItemTemplate" x:DataType="viewModelData:MailItemViewModel">
<controls:MailItemDisplayInformationControl
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}" />
</DataTemplate>
<DataTemplate x:Key="ThreadExpanderTemplate" x:DataType="viewModelData:ThreadMailItemViewModel">
<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="*" />
</Grid.ColumnDefinitions>
<!-- Expansion indicator -->
<Viewbox Width="12" Height="12">
<FontIcon
x:Name="ExpanderIcon"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xE76C;" />
</Viewbox>
<controls:MailItemDisplayInformationControl
Grid.Column="1"
x:DefaultBindMode="OneWay"
CenterHoverAction="{Binding ElementName=root, Path=ViewModel.PreferencesService.CenterHoverAction, Mode=OneWay}"
ContextRequested="MailItemContextRequested"
DataContext="{x:Bind}"
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 x:Key="ThreadMailItemTemplate" x:DataType="viewModelData:ThreadMailItemViewModel">
<TextBlock Text="thread :)" />
</DataTemplate>
<DataTemplate x:Key="DateGroupHeaderTemplate" x:DataType="data:DateGroupHeader">
<ItemContainer
CanUserSelect="UserCannotSelect"
IsHitTestVisible="False"
IsSelected="False">
<Grid Padding="12,8" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}">
<TextBlock
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
Opacity="1"
Style="{ThemeResource CaptionTextBlockStyle}"
Text="{x:Bind DisplayName, Mode=OneWay}" />
</Grid>
</ItemContainer>
</DataTemplate>
<listview:WinoMailItemTemplateSelector
x:Key="MailItemTemplateSelector"
SingleMailItemTemplate="{StaticResource SingleMailItemTemplate}"
ThreadMailItemTemplate="{StaticResource ThreadMailItemTemplate}" />
<DataTemplate x:Key="SenderGroupHeaderTemplate" x:DataType="data:SenderGroupHeader">
<ItemContainer
CanUserSelect="UserCannotSelect"
IsEnabled="False"
IsHitTestVisible="False">
<Grid Padding="12,8" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
Style="{ThemeResource SubtitleTextBlockStyle}"
Text="{x:Bind DisplayName, Mode=OneWay}" />
<StackPanel
Grid.Column="1"
Orientation="Horizontal"
Spacing="8">
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{ThemeResource CaptionTextBlockStyle}"
Text="{x:Bind ItemCount, Mode=OneWay}" />
</StackPanel>
</Grid>
</ItemContainer>
</DataTemplate>
<CollectionViewSource
x:Name="MailCollectionViewSource"
IsSourceGrouped="True"
Source="{x:Bind ViewModel.MailCollection.MailItems, Mode=OneWay}" />
<selectors1:MailItemContainerSelector
x:Name="MailItemContainerSelector"
DateGroupHeaderTemplate="{StaticResource DateGroupHeaderTemplate}"
EmailTemplate="{StaticResource EmailTemplate}"
SenderGroupHeaderTemplate="{StaticResource SenderGroupHeaderTemplate}"
ThreadExpanderTemplate="{StaticResource ThreadExpanderTemplate}" />
<DataTemplate x:Key="MailGroupHeaderDefaultTemplate" x:DataType="collections:IReadOnlyObservableGroup">
<Grid
Margin="4,2"
AllowFocusOnInteraction="False"
Background="{ThemeResource MailListHeaderBackgroundColor}"
CornerRadius="6">
<TextBlock
Padding="12"
VerticalAlignment="Center"
AllowFocusOnInteraction="False"
FontSize="13"
FontWeight="SemiBold"
Text="{x:Bind helpers:XamlHelpers.GetMailGroupDateString(Key)}" />
</Grid>
</DataTemplate>
</Page.Resources>
<Page.KeyboardAccelerators>
<KeyboardAccelerator
Key="A"
Invoked="SelectAllInvoked"
Modifiers="Control" />
<KeyboardAccelerator Key="Delete" Invoked="DeleteAllInvoked" />
</Page.KeyboardAccelerators>
<wino:BasePage.ShellContent>
<Grid HorizontalAlignment="Stretch">
<!-- Hidden focus receiver... -->
@@ -257,7 +166,7 @@
<CommandBar
HorizontalAlignment="Left"
DefaultLabelPosition="Collapsed"
IsEnabled="{x:Bind helpers:XamlHelpers.CountToBooleanConverter(ViewModel.MailCollection.SelectedVisibleItems.Count), Mode=OneWay}"
IsEnabled="{x:Bind helpers:XamlHelpers.CountToBooleanConverter(ViewModel.MailCollection.SelectedItemsCount), Mode=OneWay}"
OverflowButtonVisibility="Auto">
<interactivity:Interaction.Behaviors>
<local:BindableCommandBarBehavior ItemClickedCommand="{x:Bind ViewModel.ExecuteTopBarActionCommand}" PrimaryCommands="{x:Bind ViewModel.ActionItems, Mode=OneWay}" />
@@ -286,7 +195,6 @@
VerticalAlignment="Center"
Canvas.ZIndex="100"
Checked="SelectAllCheckboxChecked"
IsChecked="{x:Bind ViewModel.MailCollection.IsAllItemsSelected, Mode=OneWay}"
Unchecked="SelectAllCheckboxUnchecked" />
@@ -329,6 +237,7 @@
Grid.Row="1"
Grid.Column="2"
Orientation="Horizontal">
<Button Command="{x:Bind ViewModel.RemoveFirstCommand}" Content="T" />
<Button
Width="36"
Height="36"
@@ -436,7 +345,37 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<advanced:WinoItemsView
<listview:WinoListView
x:Name="MailListView"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
toolkitExt:ListViewExtensions.ItemContainerStretchDirection="Horizontal"
toolkitExt:ScrollViewerExtensions.VerticalScrollBarMargin="0"
ItemContainerStyleSelector="{StaticResource WinoMailItemContainerStyleSelector}"
ItemTemplateSelector="{StaticResource MailItemTemplateSelector}"
ItemsSource="{x:Bind MailCollectionViewSource.View, Mode=OneWay}"
ProcessKeyboardAccelerators="WinoListViewProcessKeyboardAccelerators"
SelectionMode="Extended">
<listview:WinoListView.ItemContainerTransitions>
<TransitionCollection>
<AddDeleteThemeTransition />
</TransitionCollection>
</listview:WinoListView.ItemContainerTransitions>
<listview:WinoListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Margin="8,0,12,0" AreStickyGroupHeadersEnabled="True" />
</ItemsPanelTemplate>
</listview:WinoListView.ItemsPanel>
<listview:WinoListView.Resources>
<ResourceDictionary>
<Style BasedOn="{StaticResource MailListHeaderStyle}" TargetType="ListViewHeaderItem" />
</ResourceDictionary>
</listview:WinoListView.Resources>
<listview:WinoListView.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource MailGroupHeaderDefaultTemplate}" HidesIfEmpty="True" />
</listview:WinoListView.GroupStyle>
</listview:WinoListView>
<!--<advanced:WinoItemsView
x:Name="MailListView"
Margin="4"
ItemTemplate="{StaticResource MailItemContainerSelector}"
@@ -444,7 +383,7 @@
Layout="{StaticResource DefaultItemsViewLayout}"
LoadMoreCommand="{x:Bind ViewModel.LoadMoreItemsCommand}"
ProcessKeyboardAccelerators="MailListView_ProcessKeyboardAccelerators"
SelectionMode="Extended" />
SelectionMode="Extended" />-->
<!-- Try online search panel. -->
<Grid Grid.Row="1" Visibility="{x:Bind ViewModel.IsOnlineSearchButtonVisible, Mode=OneWay}">
+47 -71
View File
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
@@ -14,7 +15,6 @@ using Microsoft.UI.Xaml.Navigation;
using MoreLinq;
using Windows.Foundation;
using Windows.System;
using Wino.Controls;
using Wino.Core.Domain;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
@@ -24,7 +24,6 @@ using Wino.Core.Domain.Models.Navigation;
using Wino.Helpers;
using Wino.Mail.ViewModels.Data;
using Wino.Mail.ViewModels.Messages;
using Wino.Mail.WinUI;
using Wino.MenuFlyouts.Context;
using Wino.Messaging.Client.Mails;
using Wino.Views.Abstract;
@@ -39,8 +38,8 @@ public sealed partial class MailListPage : MailListPageAbstract,
{
private const double RENDERING_COLUMN_MIN_WIDTH = 375;
private IStatePersistanceService StatePersistenceService { get; } = App.Current.Services.GetService<IStatePersistanceService>();
private IKeyPressService KeyPressService { get; } = App.Current.Services.GetService<IKeyPressService>();
private IStatePersistanceService StatePersistenceService { get; } = Core.WinUI.WinoApplication.Current.Services.GetService<IStatePersistanceService>() ?? throw new Exception($"Can't resolve {nameof(KeyPressService)}");
private IKeyPressService KeyPressService { get; } = Core.WinUI.WinoApplication.Current.Services.GetService<IKeyPressService>() ?? throw new Exception($"Can't resolve {nameof(KeyPressService)}");
public MailListPage()
{
@@ -53,27 +52,26 @@ public sealed partial class MailListPage : MailListPageAbstract,
Bindings.Update();
ViewModel.MailCollection.ItemSelectionChanged += WinoMailCollectionSelectionChanged;
UpdateSelectAllButtonStatus();
UpdateAdaptiveness();
// Delegate to ViewModel.
if (e.Parameter is NavigateMailFolderEventArgs folderNavigationArgs)
{
WeakReferenceMessenger.Default.Send(new ActiveMailFolderChangedEvent(folderNavigationArgs.BaseFolderMenuItem, folderNavigationArgs.FolderInitLoadAwaitTask));
}
ViewModel.ThrottledSelectionChanged += ListSelectionChanged;
}
private void ListSelectionChanged(object? sender, EventArgs e)
{
UpdateSelectAllButtonStatus();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
ViewModel.ThrottledSelectionChanged -= ListSelectionChanged;
this.Bindings.StopTracking();
ViewModel.MailCollection.ItemSelectionChanged -= WinoMailCollectionSelectionChanged;
RenderingFrame.Navigate(typeof(IdlePage));
GC.Collect();
@@ -84,20 +82,19 @@ public sealed partial class MailListPage : MailListPageAbstract,
// Check all checkbox if all is selected.
// Unhook events to prevent selection overriding.
//DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
//{
// SelectAllCheckbox.Checked -= SelectAllCheckboxChecked;
// SelectAllCheckbox.Unchecked -= SelectAllCheckboxUnchecked;
DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
{
SelectAllCheckbox.Checked -= SelectAllCheckboxChecked;
SelectAllCheckbox.Unchecked -= SelectAllCheckboxUnchecked;
// bool isAllSelected = ViewModel.MailCollection.AllItems.All(a => a.IsSelected);
// SelectAllCheckbox.IsChecked = ViewModel.MailCollection.AllItems.Count() > 0 && isAllSelected;
SelectAllCheckbox.IsChecked = ViewModel.MailCollection.IsAllItemsSelected;
// SelectAllCheckbox.Checked += SelectAllCheckboxChecked;
// SelectAllCheckbox.Unchecked += SelectAllCheckboxUnchecked;
//});
SelectAllCheckbox.Checked += SelectAllCheckboxChecked;
SelectAllCheckbox.Unchecked += SelectAllCheckboxUnchecked;
});
}
private void SelectionModeToggleChecked(object sender, RoutedEventArgs e) => ChangeSelectionMode(ItemsViewSelectionMode.Multiple);
private void SelectionModeToggleChecked(object sender, RoutedEventArgs e) => ChangeSelectionMode(ListViewSelectionMode.Multiple);
private void FolderPivotChanged(object sender, SelectionChangedEventArgs e)
{
@@ -124,29 +121,29 @@ public sealed partial class MailListPage : MailListPageAbstract,
ViewModel.SelectedPivotChangedCommand.Execute(null);
}
private void ChangeSelectionMode(ItemsViewSelectionMode mode)
private void ChangeSelectionMode(ListViewSelectionMode mode)
{
MailListView.SelectionMode = mode;
if (ViewModel?.PivotFolders != null)
{
ViewModel.PivotFolders.ForEach(a => a.IsExtendedMode = mode == ItemsViewSelectionMode.Extended);
ViewModel.PivotFolders.ForEach(a => a.IsExtendedMode = mode == ListViewSelectionMode.Extended);
}
}
private void SelectionModeToggleUnchecked(object sender, RoutedEventArgs e)
{
ChangeSelectionMode(ItemsViewSelectionMode.Extended);
ChangeSelectionMode(ListViewSelectionMode.Extended);
}
private void SelectAllCheckboxChecked(object sender, RoutedEventArgs e)
private async void SelectAllCheckboxChecked(object sender, RoutedEventArgs e)
{
ViewModel.MailCollection.SelectAll();
await ViewModel.MailCollection.SelectAllAsync();
}
private void SelectAllCheckboxUnchecked(object sender, RoutedEventArgs e)
private async void SelectAllCheckboxUnchecked(object sender, RoutedEventArgs e)
{
ViewModel.MailCollection.ClearSelections();
await ViewModel.MailCollection.UnselectAllAsync();
}
private async void MailItemContextRequested(UIElement sender, ContextRequestedEventArgs args)
@@ -156,24 +153,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))
{
if (control.DataContext is MailItemViewModel clickedMailItemContext)
{
var targetItems = ViewModel.MailCollection.AllItems.Where(a => a.IsSelected);
var availableActions = ViewModel.GetAvailableMailActions(targetItems);
//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 (!availableActions?.Any() ?? false) return;
// if (!availableActions?.Any() ?? false) return;
var clickedOperation = await GetMailOperationFromFlyoutAsync(availableActions, control, p.X, p.Y);
// var clickedOperation = await GetMailOperationFromFlyoutAsync(availableActions, control, p.X, p.Y);
if (clickedOperation == null) return;
// if (clickedOperation == null) return;
var prepRequest = new MailOperationPreperationRequest(clickedOperation.Operation, targetItems.Select(a => a.MailCopy));
// 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,
@@ -277,7 +274,7 @@ public sealed partial class MailListPage : MailListPageAbstract,
#region Connected Animation Helpers
private WebView2 GetRenderingPageWebView()
private WebView2? GetRenderingPageWebView()
{
if (RenderingFrame.Content is MailRenderingPage renderingPage)
return renderingPage.GetWebView();
@@ -285,7 +282,7 @@ public sealed partial class MailListPage : MailListPageAbstract,
return null;
}
private WebView2 GetComposerPageWebView()
private WebView2? GetComposerPageWebView()
{
if (RenderingFrame.Content is ComposePage composePage)
return composePage.GetWebView();
@@ -301,7 +298,7 @@ public sealed partial class MailListPage : MailListPageAbstract,
await ViewModel.ExecuteUIThread(async () =>
{
MailListView.ClearSelections(message.SelectedMailViewModel, true);
// MailListView.ClearSelections(message.SelectedMailViewModel, true);
int retriedSelectionCount = 0;
trySelection:
@@ -343,7 +340,7 @@ public sealed partial class MailListPage : MailListPageAbstract,
if (scrollIndex >= 0)
{
MailListView.StartBringItemIntoView(scrollIndex, new BringIntoViewOptions() { AnimationDesired = true });
await MailListView.SmoothScrollIntoViewWithIndexAsync(scrollIndex);
}
}
});
@@ -436,7 +433,6 @@ public sealed partial class MailListPage : MailListPageAbstract,
}
}
private async void SearchBar_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
// User clicked 'x' button to clearout the search text.
@@ -510,13 +506,6 @@ public sealed partial class MailListPage : MailListPageAbstract,
}
}
private void SelectAllInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
{
// MailListView.SelectAllWino();
}
private void DeleteAllInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
=> ViewModel.ExecuteMailOperationCommand.Execute(MailOperation.SoftDelete);
@@ -552,7 +541,7 @@ public sealed partial class MailListPage : MailListPageAbstract,
visual.StartAnimation("RotationAngleInDegrees", rotationAnimation);
}
private void ListSelectionChanged(ItemsView sender, ItemsViewSelectionChangedEventArgs args)
private void WinoMailCollectionSelectionChanged(object sender, EventArgs args)
{
UpdateSelectAllButtonStatus();
UpdateAdaptiveness();
@@ -565,7 +554,7 @@ public sealed partial class MailListPage : MailListPageAbstract,
expander.IsThreadExpanded = !expander.IsThreadExpanded;
// Select all.
ViewModel.MailCollection.AllItems.Where(a => expander.ThreadEmails.Contains(a)).ForEach(a => a.IsSelected = true);
// ViewModel.MailCollection.AllItems.Where(a => expander.ThreadEmails.Contains(a)).ForEach(a => a.IsSelected = true);
}
}
@@ -586,7 +575,7 @@ public sealed partial class MailListPage : MailListPageAbstract,
}
}
private void MailListView_ProcessKeyboardAccelerators(UIElement sender, ProcessKeyboardAcceleratorEventArgs args)
private async void WinoListViewProcessKeyboardAccelerators(UIElement sender, ProcessKeyboardAcceleratorEventArgs args)
{
if (args.Key == VirtualKey.Delete)
{
@@ -594,22 +583,9 @@ public sealed partial class MailListPage : MailListPageAbstract,
}
else
{
// ItemsView have weird logic for selection inversion. We need to handle it manually.
args.Handled = true;
// TODO: If not all items are selected, select all. Otherwise clear selection.
// Handle selections in the GroupedEmailCollection.
ViewModel.MailCollection.SelectAll();
//if (!ViewModel.MailCollection.IsAllItemsSelected)
//{
//}
//else
//{
// ViewModel.MailCollection.ClearSelections();
//}
await ViewModel.MailCollection.ToggleSelectAllAsync();
}
}
}
+6
View File
@@ -66,6 +66,7 @@
<Content Remove="Assets\Wide310x150Logo.scale-400.png" />
</ItemGroup>
<ItemGroup>
<None Remove="Controls\ListView\WinoListViewStyles.xaml" />
<None Remove="ShellWindow.xaml" />
</ItemGroup>
@@ -123,6 +124,11 @@
<ProjectReference Include="..\Wino.Mail.ViewModels\Wino.Mail.ViewModels.csproj" />
<ProjectReference Include="..\Wino.Services\Wino.Services.csproj" />
</ItemGroup>
<ItemGroup>
<Page Update="Controls\ListView\WinoListViewStyles.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="ShellWindow.xaml">
<Generator>MSBuild:Compile</Generator>