Skip to content
Open
  •  
  •  
  •  
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<WindowsSdkPackageVersion>10.0.26100.67-preview</WindowsSdkPackageVersion>
<WindowsTargetFramework>$(TargetFrameworkVersion)-windows$(TargetWindowsVersion)</WindowsTargetFramework>
<LangVersion>preview</LangVersion>
<WarningsAsErrors>nullable</WarningsAsErrors>

<!-- Use Microsoft.Testing.Platform -->
<EnableMSTestRunner>true</EnableMSTestRunner>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ public interface IOmnibarTextMemberPathProvider
/// Retrieves the path of the text member as a string. This path can be used to identify the location of the text member.
/// </summary>
/// <returns>Returns a string representing the path of the text member.</returns>
string GetTextMemberPath(string textMemberPath);
string? GetTextMemberPath(string textMemberPath);
}
}
2 changes: 1 addition & 1 deletion src/Files.App.Controls/Omnibar/Omnibar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ private string GetObjectText(object obj)
return obj is string text
? text
: obj is IOmnibarTextMemberPathProvider textMemberPathProvider
? textMemberPathProvider.GetTextMemberPath(CurrentSelectedMode.TextMemberPath ?? string.Empty)
? textMemberPathProvider.GetTextMemberPath(CurrentSelectedMode.TextMemberPath ?? string.Empty) ?? string.Empty
: obj.ToString() ?? string.Empty;
}

Expand Down
14 changes: 14 additions & 0 deletions src/Files.App.Controls/Sidebar/ISidebarItemModel.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

using Microsoft.UI.Xaml;

namespace Files.App.Controls
{
public interface ISidebarItemPresentationModel
{
string? Text { get; }

object? ToolTip { get; }

FrameworkElement? IconElement { get; }

FrameworkElement? ItemDecorator { get; }
}

public interface ISidebarItemModel : INotifyPropertyChanged
{

/// <summary>
/// The children of this item that will be rendered as child elements of the SidebarItem
/// </summary>
Expand Down
16 changes: 15 additions & 1 deletion src/Files.App.Controls/Sidebar/SidebarItem.Properties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,15 @@ public ISidebarItemModel? Item
set { SetValue(ItemProperty, value); }
}
public static readonly DependencyProperty ItemProperty =
DependencyProperty.Register(nameof(Item), typeof(ISidebarItemModel), typeof(SidebarItem), new PropertyMetadata(null));
DependencyProperty.Register(nameof(Item), typeof(ISidebarItemModel), typeof(SidebarItem), new PropertyMetadata(null, OnPropertyChanged));

public bool UseItemPresentation
{
get { return (bool)GetValue(UseItemPresentationProperty); }
set { SetValue(UseItemPresentationProperty, value); }
}
public static readonly DependencyProperty UseItemPresentationProperty =
DependencyProperty.Register(nameof(UseItemPresentation), typeof(bool), typeof(SidebarItem), new PropertyMetadata(false, OnPropertyChanged));

public bool UseReorderDrop
{
Expand Down Expand Up @@ -136,12 +144,18 @@ public static void OnPropertyChanged(DependencyObject sender, DependencyProperty
}
else if (e.Property == IsExpandedProperty)
{
if (item.UseItemPresentation && item.Item is { } model && model.IsExpanded != item.IsExpanded)
model.IsExpanded = item.IsExpanded;
item.UpdateExpansionState();
}
else if (e.Property == ItemProperty)
{
item.HandleItemChange();
}
else if (e.Property == UseItemPresentationProperty && item.UseItemPresentation)
{
item.UpdateItemPresentation();
}
else
{
Debug.Write(e.Property.ToString());
Expand Down
60 changes: 60 additions & 0 deletions src/Files.App.Controls/Sidebar/SidebarItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ protected override void OnApplyTemplate()
if (GetTemplateChild("ChevronContainer") is Border chevronContainer)
chevronContainer.PointerPressed += ChevronContainer_PointerPressed;
if (GetTemplateChild("FlyoutChildrenPresenter") is ItemsRepeater flyoutRepeater)
{
flyoutRepeater.ElementPrepared += FlyoutChildrenPresenter_ElementPrepared;
flyoutRepeater.ItemsSource = Item?.Children;
}
}

if (Owner is null)
Expand Down Expand Up @@ -124,11 +127,31 @@ private void SidebarItem_Loaded(object sender, RoutedEventArgs e)
public void HandleItemChange()
{
HookupItemChangeListener(null, Item);
if (UseItemPresentation)
UpdateItemPresentation();
UpdateFlyoutChildrenSource();
UpdateExpansionState();
ReevaluateSelection();
CanDrag = Item?.Path is string path && Path.IsPathRooted(path);
}

private void UpdateItemPresentation()
{
var presentation = Item as ISidebarItemPresentationModel;
AutomationProperties.SetAutomationId(this, presentation?.Text ?? string.Empty);
Text = presentation?.Text;
ToolTip = presentation?.ToolTip;
Icon = presentation?.IconElement;
Decorator = presentation?.ItemDecorator;
IsExpanded = Item?.IsExpanded ?? true;
}

private void UpdateFlyoutChildrenSource()
{
if (GetTemplateChild("FlyoutChildrenPresenter") is ItemsRepeater flyoutRepeater)
flyoutRepeater.ItemsSource = Item?.Children;
}

private void HookupOwners()
{
// Owner is pushed in by the hosting SidebarView's MenuItemsHost_ElementPrepared (top-level rows) or the parent SidebarItem's FlyoutChildrenPresenter_ElementPrepared (flyout children) before Loaded fires. Static SidebarItems declared directly in XAML (MainPage's SettingsButton in SidebarView.Footer) aren't realized through either path, so resolve Owner via a visual-tree walk for them. OwnerExpansionSupport state is applied by OnOwnerChanged.
Expand Down Expand Up @@ -181,9 +204,46 @@ private void Item_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case null:
case "":
if (UseItemPresentation)
UpdateItemPresentation();
UpdateFlyoutChildrenSource();
UpdateExpansionState();
ReevaluateSelection();
break;
case nameof(ISidebarItemPresentationModel.Text):
if (UseItemPresentation)
{
var presentation = Item as ISidebarItemPresentationModel;
Text = presentation?.Text;
AutomationProperties.SetAutomationId(this, presentation?.Text ?? string.Empty);
}
break;
case nameof(ISidebarItemPresentationModel.ToolTip):
if (UseItemPresentation)
ToolTip = (Item as ISidebarItemPresentationModel)?.ToolTip;
break;
case nameof(ISidebarItemPresentationModel.IconElement):
if (UseItemPresentation)
Icon = (Item as ISidebarItemPresentationModel)?.IconElement;
break;
case nameof(ISidebarItemPresentationModel.ItemDecorator):
if (UseItemPresentation)
Decorator = (Item as ISidebarItemPresentationModel)?.ItemDecorator;
break;
case nameof(ISidebarItemModel.IsExpanded):
if (UseItemPresentation)
IsExpanded = Item?.IsExpanded ?? true;
UpdateExpansionState();
break;
case nameof(ISidebarItemModel.HasUnrealizedChildren):
case nameof(ISidebarItemModel.IsLeafWithChildren):
UpdateExpansionState();
ReevaluateSelection();
break;
case nameof(ISidebarItemModel.Children):
UpdateFlyoutChildrenSource();
UpdateExpansionState();
ReevaluateSelection();
break;
Expand Down
22 changes: 1 addition & 21 deletions src/Files.App.Controls/Sidebar/SidebarStyles.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,6 @@
<Setter Target="Padding" Value="0" />
</Style>

<DataTemplate x:Key="DefaultSidebarItemTemplate">
<local:SidebarItem
AutomationProperties.AutomationId="{Binding Item.Text}"
ContentOpacity="{Binding RowOpacity}"
Decorator="{Binding Item.ItemDecorator}"
Icon="{Binding Item.IconElement}"
IsExpanded="{Binding Item.IsExpanded, Mode=TwoWay}"
Item="{Binding Item}"
Margin="{Binding SectionGapMargin}"
NestingLevel="{Binding Depth}"
Text="{Binding Item.Text}"
ToolTip="{Binding Item.ToolTip}" />
</DataTemplate>

<Style TargetType="local:SidebarItem">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Template">
Expand Down Expand Up @@ -211,19 +197,13 @@
x:Name="FlyoutChildrenPresenter"
HorizontalAlignment="Stretch"
AutomationProperties.AccessibilityView="Content"
ItemsSource="{Binding Item.Children, Mode=OneWay}"
XYFocusKeyboardNavigation="Enabled">
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<local:SidebarItem
AutomationProperties.AutomationId="{Binding Text}"
Decorator="{Binding ItemDecorator}"
Icon="{Binding IconElement}"
IsExpanded="{Binding IsExpanded, Mode=TwoWay}"
IsInFlyout="True"
Item="{Binding}"
Text="{Binding Text}"
ToolTip="{Binding ToolTip}" />
UseItemPresentation="True" />
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
Expand Down
15 changes: 15 additions & 0 deletions src/Files.App.Controls/Sidebar/SidebarView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,27 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Files.App.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
HorizontalAlignment="Stretch"
Loaded="SidebarView_Loaded"
TabFocusNavigation="Local"
mc:Ignorable="d">

<UserControl.Resources>
<DataTemplate
x:Key="DefaultSidebarItemTemplate"
x:DataType="local:FlatSidebarItem">
<local:SidebarItem
ContentOpacity="{x:Bind RowOpacity}"
DataContext="{x:Bind}"
Item="{x:Bind Item}"
Margin="{x:Bind SectionGapMargin, Mode=OneWay}"
NestingLevel="{x:Bind Depth}"
UseItemPresentation="True" />
</DataTemplate>
</UserControl.Resources>

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition
Expand Down
2 changes: 1 addition & 1 deletion src/Files.App.Launcher/Files.App.Launcher.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
<PropertyGroup Condition="'$(Configuration)'!='Debug'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<PlatformToolset>v145</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
<LinkIncremental>false</LinkIncremental>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,29 @@ public BaseCompressArchiveAction()

protected void GetDestination(out string[] sources, out string directory, out string fileName)
{
sources = context.SelectedItems.Select(item => item.ItemPath).ToArray();
sources = context.SelectedItems
.Select(item => item.GetRequiredPath())
.ToArray();
directory = string.Empty;
fileName = string.Empty;

if (sources.Length is not 0)
{
// Get the current directory path
directory = context.ShellPage.ShellViewModel.WorkingDirectory.Normalize();
var shellPage = context.ShellPage ?? throw new InvalidOperationException("An active shell page is required to compress items.");
var shellViewModel = shellPage.GetRequiredShellViewModel();
var workingDirectory = shellViewModel.WorkingDirectory
?? throw new InvalidOperationException("The active shell page does not have a working directory.");
directory = workingDirectory.Normalize();

// Get the library save folder if the folder is library item
if (App.LibraryManager.TryGetLibrary(directory, out var library) && !library.IsEmpty)
directory = library.DefaultSaveFolder;
directory = library.DefaultSaveFolder
?? throw new InvalidOperationException("The library does not have a default save folder.");

// Gets the file name from the directory path
fileName = SystemIO.Path.GetFileName(sources.Length is 1 ? sources[0] : directory);
fileName = SystemIO.Path.GetFileName(sources.Length is 1 ? sources[0] : directory)
?? throw new InvalidOperationException("The archive destination does not have a file name.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ protected async Task DecompressArchiveHereAsync(bool smart = false)
return;

var selectedItems = context.SelectedItems.ToList();
var currentFolderPath = context.ShellPage?.ShellViewModel.CurrentFolder?.ItemPath ?? string.Empty;
BaseStorageFolder currentFolder = await StorageHelpers.ToStorageItem<BaseStorageFolder>(currentFolderPath);
var currentFolderPath = context.ShellPage?.ShellViewModel?.CurrentFolder?.ItemPath ?? string.Empty;
BaseStorageFolder? currentFolder = await StorageHelpers.ToStorageItem<BaseStorageFolder>(currentFolderPath);

foreach (var selectedItem in selectedItems)
{
var password = string.Empty;
BaseStorageFile archive = await StorageHelpers.ToStorageItem<BaseStorageFile>(selectedItem.ItemPath);
BaseStorageFile? archive = await StorageHelpers.ToStorageItem<BaseStorageFile>(selectedItem.ItemPath!);

if (archive?.Path is null)
return;
Expand Down Expand Up @@ -140,7 +140,7 @@ static ReadOnlySpan<char> GetFirstMeaningfulSegment(ReadOnlySpan<char> path)
if (smart && currentFolder is not null && isMultipleItems)
{
destinationFolder =
await FilesystemTasks.Wrap(() =>
await FilesystemTasks.WrapNullable(() =>
currentFolder.CreateFolderAsync(
SystemIO.Path.GetFileNameWithoutExtension(archive.Path),
CreationCollisionOption.GenerateUniqueName).AsTask());
Expand All @@ -152,7 +152,7 @@ await FilesystemTasks.Wrap(() =>

// Operate decompress
var result = await FilesystemTasks.Wrap(() =>
StorageArchiveService.DecompressAsync(selectedItem.ItemPath, destinationFolder?.Path ?? string.Empty, password));
StorageArchiveService.DecompressAsync(selectedItem.ItemPath!, destinationFolder?.Path ?? string.Empty, password));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public override async Task ExecuteAsync(object? parameter = null)
if (string.IsNullOrEmpty(archivePath))
return;

BaseStorageFile archive = await StorageHelpers.ToStorageItem<BaseStorageFile>(archivePath);
BaseStorageFile? archive = await StorageHelpers.ToStorageItem<BaseStorageFile>(archivePath);

if (archive?.Path is null)
return;
Expand Down Expand Up @@ -83,21 +83,24 @@ public override async Task ExecuteAsync(object? parameter = null)
if (!StorageHelpers.Exists(archive.Path))
return;

BaseStorageFolder destinationFolder = decompressArchiveViewModel.DestinationFolder;
BaseStorageFolder? destinationFolder = decompressArchiveViewModel.DestinationFolder;
string destinationFolderPath = decompressArchiveViewModel.DestinationFolderPath;

// Save extraction location for future use
SaveExtractionLocation(destinationFolderPath);

if (destinationFolder is null)
{
BaseStorageFolder parentFolder = await StorageHelpers.ToStorageItem<BaseStorageFolder>(Path.GetDirectoryName(archive.Path) ?? string.Empty);
destinationFolder = await FilesystemTasks.Wrap(() => parentFolder.CreateFolderAsync(Path.GetFileName(destinationFolderPath), CreationCollisionOption.GenerateUniqueName).AsTask());
BaseStorageFolder? parentFolder = await StorageHelpers.ToStorageItem<BaseStorageFolder>(Path.GetDirectoryName(archive.Path) ?? string.Empty);
if (parentFolder is null)
throw new InvalidOperationException("The archive's parent folder could not be resolved.");

destinationFolder = await FilesystemTasks.WrapNullable(() => parentFolder.CreateFolderAsync(Path.GetFileName(destinationFolderPath), CreationCollisionOption.GenerateUniqueName).AsTask());
}

// Operate decompress
var result = await FilesystemTasks.Wrap(() =>
StorageArchiveService.DecompressAsync(archive?.Path ?? string.Empty, destinationFolder?.Path ?? string.Empty, password, encoding));
StorageArchiveService.DecompressAsync(archive.Path, destinationFolder?.Path ?? string.Empty, password, encoding));

if (decompressArchiveViewModel.OpenDestinationFolderOnCompletion)
await NavigationHelpers.OpenPath(destinationFolderPath, context.ShellPage, FilesystemItemType.Directory);
Expand Down
Loading