Skip to content

Fix: Fixed an issue where folder icon customization apply before saving #12141

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 74 additions & 104 deletions src/Files.App/ViewModels/Properties/CustomizationViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,125 +1,79 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.WinUI;
using CommunityToolkit.WinUI;
using Files.App.Shell;
using Files.App.Helpers;
using Files.App.Views.Properties;
using Files.Shared;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Threading.Tasks;
using Windows.Storage.Pickers;
using Microsoft.UI.Windowing;

namespace Files.App.ViewModels.Properties
{
public class CustomizationViewModel : ObservableObject
{
public CustomizationViewModel(IShellPage appInstance, BaseProperties baseProperties, AppWindow appWindow)
{
Filesystem.ListedItem item;

if (baseProperties is FileProperties fileProperties)
item = fileProperties.Item;
else if (baseProperties is FolderProperties folderProperties)
item = folderProperties.Item;
else
return;

AppInstance = appInstance;
AppWindow = appWindow;
IconResourceItemPath = Path.Combine(CommonPaths.SystemRootPath, "System32", "SHELL32.dll");
IsShortcut = item.IsShortcut;
SelectedItemPath = item.ItemPath;

_dllIcons = new();
DllIcons = new(_dllIcons);

// Get default
LoadIconsForPath(IconResourceItemPath);
private static string DefaultIconDllFilePath
=> Path.Combine(CommonPaths.SystemRootPath, "System32", "SHELL32.dll");

RestoreDefaultIconCommand = new AsyncRelayCommand(ExecuteRestoreDefaultIconAsync);
PickDllFileCommand = new AsyncRelayCommand(ExecuteOpenFilePickerAsync);
}
private readonly AppWindow _appWindow;

private AppWindow AppWindow;
private readonly IShellPage _appInstance;

private string? _selectedItemPath;
public string? SelectedItemPath
{
get => _selectedItemPath;
set => SetProperty(ref _selectedItemPath, value);
}
private readonly string _selectedItemPath;

private string? _iconResourceItemPath;
public string? IconResourceItemPath
{
get => _iconResourceItemPath;
set => SetProperty(ref _iconResourceItemPath, value);
}
private bool _isIconChanged;

private IShellPage? _appInstance;
private IShellPage? AppInstance
{
get => _appInstance;
set => SetProperty(ref _appInstance, value);
}
public readonly bool IsShortcut;

private bool _isShortcut;
public bool IsShortcut
{
get => _isShortcut;
private set => SetProperty(ref _isShortcut, value);
}
public ObservableCollection<IconFileInfo> DllIcons { get; }

private bool _restoreButtonIsEnabled = true;
public bool RestoreButtonIsEnabled
private string _IconResourceItemPath;
public string IconResourceItemPath
{
get => _restoreButtonIsEnabled;
private set => SetProperty(ref _restoreButtonIsEnabled, value);
get => _IconResourceItemPath;
set => SetProperty(ref _IconResourceItemPath, value);
}

private readonly ObservableCollection<IconFileInfo> _dllIcons;
public ReadOnlyObservableCollection<IconFileInfo> DllIcons { get; }

private IconFileInfo _SelectedDllIcon;
public IconFileInfo SelectedDllIcon
private IconFileInfo? _SelectedDllIcon;
public IconFileInfo? SelectedDllIcon
{
get => _SelectedDllIcon;
set
{
if (value is not null && SetProperty(ref _SelectedDllIcon, value))
{
ChangeIcon(value);
}
if (SetProperty(ref _SelectedDllIcon, value))
_isIconChanged = true;
}
}

public IAsyncRelayCommand RestoreDefaultIconCommand { get; private set; }
public IAsyncRelayCommand PickDllFileCommand { get; private set; }
public IRelayCommand RestoreDefaultIconCommand { get; private set; }
public IAsyncRelayCommand OpenFilePickerCommand { get; private set; }

private async Task ExecuteRestoreDefaultIconAsync()
public CustomizationViewModel(IShellPage appInstance, BaseProperties baseProperties, AppWindow appWindow)
{
RestoreButtonIsEnabled = false;
ListedItem item;

var setIconResult = IsShortcut
? Win32API.SetCustomFileIcon(SelectedItemPath, null)
: Win32API.SetCustomDirectoryIcon(SelectedItemPath, null);
if (baseProperties is FileProperties fileProperties)
item = fileProperties.Item;
else if (baseProperties is FolderProperties folderProperties)
item = folderProperties.Item;
else
return;

if (setIconResult)
{
await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() =>
{
AppInstance?.FilesystemViewModel?.RefreshItems(null, async () =>
{
await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => RestoreButtonIsEnabled = true);

});
});
}
_appInstance = appInstance;
_appWindow = appWindow;
IconResourceItemPath = DefaultIconDllFilePath;
IsShortcut = item.IsShortcut;
_selectedItemPath = item.ItemPath;

DllIcons = new();

// Get default
LoadIconsForPath(IconResourceItemPath);

RestoreDefaultIconCommand = new RelayCommand(ExecuteRestoreDefaultIcon);
OpenFilePickerCommand = new AsyncRelayCommand(ExecuteOpenFilePickerAsync);
}

private void ExecuteRestoreDefaultIcon()
{
SelectedDllIcon = null;
_isIconChanged = true;
}

private async Task ExecuteOpenFilePickerAsync()
Expand All @@ -136,7 +90,7 @@ private async Task ExecuteOpenFilePickerAsync()
picker.FileTypeFilter.Add(".ico");

// WINUI3: Create and initialize new window
var parentWindowId = AppWindow.Id;
var parentWindowId = _appWindow.Id;
var handle = Microsoft.UI.Win32Interop.GetWindowFromWindowId(parentWindowId);
WinRT.Interop.InitializeWithWindow.Initialize(picker, handle);

Expand All @@ -148,32 +102,48 @@ private async Task ExecuteOpenFilePickerAsync()
LoadIconsForPath(file.Path);
}

private async Task ChangeIcon(IconFileInfo selectedIconInfo)
public async Task<bool> UpdateIcon()
{
var setIconResult = IsShortcut
? Win32API.SetCustomFileIcon(SelectedItemPath, IconResourceItemPath, selectedIconInfo.Index)
: Win32API.SetCustomDirectoryIcon(SelectedItemPath, IconResourceItemPath, selectedIconInfo.Index);
if (!_isIconChanged)
return false;

bool result = false;

if (setIconResult)
if (SelectedDllIcon is null)
{
result = IsShortcut
? Win32API.SetCustomFileIcon(_selectedItemPath, null)
: Win32API.SetCustomDirectoryIcon(_selectedItemPath, null);
}
else
{
await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() =>
{
AppInstance?.FilesystemViewModel?.RefreshItems(null);
});
result = IsShortcut
? Win32API.SetCustomFileIcon(_selectedItemPath, IconResourceItemPath, SelectedDllIcon.Index)
: Win32API.SetCustomDirectoryIcon(_selectedItemPath, IconResourceItemPath, SelectedDllIcon.Index);
}

if (!result)
return false;

await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() =>
{
_appInstance?.FilesystemViewModel?.RefreshItems(null);
});

return true;
}

private void LoadIconsForPath(string path)
{
IconResourceItemPath = path;
_dllIcons.Clear();
DllIcons.Clear();

var icons = Win32API.ExtractIconsFromDLL(path);
if (icons?.Count is null or 0)
return;

foreach(var item in icons)
_dllIcons.Add(item);
DllIcons.Add(item);
}
}
}
7 changes: 3 additions & 4 deletions src/Files.App/Views/Properties/CustomizationPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@
Grid.Column="1"
x:Load="{x:Bind CustomizationViewModel.IsShortcut, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
Command="{x:Bind CustomizationViewModel.RestoreDefaultIconCommand, Mode=OneWay}"
Content="{helpers:ResourceString Name=RestoreDefault}"
IsEnabled="{x:Bind CustomizationViewModel.RestoreButtonIsEnabled, Mode=OneWay}" />
Content="{helpers:ResourceString Name=RestoreDefault}" />

</Grid>

Expand Down Expand Up @@ -90,13 +89,13 @@
<Button
x:Name="PickDllFileButton"
Grid.Column="1"
Command="{x:Bind CustomizationViewModel.PickDllFileCommand, Mode=OneWay}"
Command="{x:Bind CustomizationViewModel.OpenFilePickerCommand, Mode=OneWay}"
CommandParameter="{x:Bind XamlRoot, Mode=OneWay}"
Content="{helpers:ResourceString Name=Browse}" />

</Grid>

<!-- Icon List -->
<!-- Dll Icon List -->
<GridView
x:Name="IconSelectionGrid"
Grid.Row="3"
Expand Down
6 changes: 2 additions & 4 deletions src/Files.App/Views/Properties/CustomizationPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
// Copyright (c) 2023 Files Community
// Licensed under the MIT License. See the LICENSE.

using Files.App.DataModels;
using Files.App.ViewModels.Properties;
using Microsoft.UI.Xaml.Navigation;
using System.Threading.Tasks;

namespace Files.App.Views.Properties
{
Expand All @@ -26,8 +24,8 @@ protected override void OnNavigatedTo(NavigationEventArgs e)
CustomizationViewModel = new(AppInstance, BaseProperties, parameter.AppWindow);
}

public override Task<bool> SaveChangesAsync()
=> Task.FromResult(true);
public async override Task<bool> SaveChangesAsync()
=> await CustomizationViewModel.UpdateIcon();

public override void Dispose()
{
Expand Down