Skip to content

Commit 31050e2

Browse files
committed
Improve
1 parent 2dbf6f4 commit 31050e2

File tree

3 files changed

+175
-69
lines changed

3 files changed

+175
-69
lines changed

src/Files.App/Services/Windows/WindowsQuickAccessService.cs

Lines changed: 141 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -54,63 +54,13 @@ public async Task InitializeAsync()
5454

5555
public async Task<bool> UpdatePinnedFoldersAsync()
5656
{
57-
return await Task.Run(UpdatePinnedFolders);
58-
59-
unsafe bool UpdatePinnedFolders()
57+
return await Task.Run(() =>
6058
{
6159
try
6260
{
63-
HRESULT hr = default;
64-
65-
string szFolderShellPath = "Shell:::{3936E9E4-D92C-4EEE-A85A-BC16D5EA0819}";
66-
67-
// Get IShellItem of the shell folder
68-
var shellItemIid = typeof(IShellItem).GUID;
69-
using ComPtr<IShellItem> pFolderShellItem = default;
70-
fixed (char* pszFolderShellPath = szFolderShellPath)
71-
hr = PInvoke.SHCreateItemFromParsingName(pszFolderShellPath, null, &shellItemIid, (void**)pFolderShellItem.GetAddressOf());
72-
73-
// Get IEnumShellItems of the quick access shell folder
74-
var enumItemsBHID = PInvoke.BHID_EnumItems;
75-
Guid enumShellItemIid = typeof(IEnumShellItems).GUID;
76-
using ComPtr<IEnumShellItems> pEnumShellItems = default;
77-
hr = pFolderShellItem.Get()->BindToHandler(null, &enumItemsBHID, &enumShellItemIid, (void**)pEnumShellItems.GetAddressOf());
78-
79-
// Enumerate recent items and populate the list
80-
int index = 0;
8161
List<LocationItem> items = [];
82-
using ComPtr<IShellItem> pShellItem = default;
83-
while (pEnumShellItems.Get()->Next(1, pShellItem.GetAddressOf()) == HRESULT.S_OK)
84-
{
85-
// Get top 20 items
86-
if (index is 20)
87-
break;
88-
89-
// Get whether the item is pined or not
90-
using ComPtr<IShellItem2> pShellItem2 = pShellItem.As<IShellItem2>(typeof(IShellItem2).GUID);
91-
hr = PInvoke.PSGetPropertyKeyFromName("System.Home.IsPinned", out var propertyKey);
92-
hr = pShellItem2.Get()->GetString(propertyKey, out var szPropertyValue);
93-
if (bool.TryParse(szPropertyValue.ToString(), out var isPinned) && !isPinned)
94-
continue;
95-
96-
// Get the target path
97-
pShellItem.Get()->GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEEDITING, out var szDisplayName);
98-
var targetPath = szDisplayName.ToString();
99-
PInvoke.CoTaskMemFree(szDisplayName.Value);
100-
101-
// Get the display name
102-
pShellItem.Get()->GetDisplayName(SIGDN.SIGDN_NORMALDISPLAY, out szDisplayName);
103-
var fileName = szDisplayName.ToString();
104-
PInvoke.CoTaskMemFree(szDisplayName.Value);
105-
106-
items.Add(new()
107-
{
108-
Path = targetPath,
109-
Text = fileName,
110-
});
111-
112-
index++;
113-
}
62+
foreach (var path in GetPinnedFolders())
63+
items.Add(await CreateItemOf(path));
11464

11565
if (items.Count is 0)
11666
return false;
@@ -123,16 +73,149 @@ unsafe bool UpdatePinnedFolders()
12373
_PinnedFolders.AddRange(items);
12474
}
12575

126-
//var eventArgs = GetChangedActionEventArgs(snapshot, items);
76+
var eventArgs = GetChangedActionEventArgs(snapshot, items);
77+
PinnedFoldersChanged?.Invoke(this, eventArgs);
78+
}
79+
catch
80+
{
81+
}
82+
});
83+
84+
unsafe IEnumerable<string> GetPinnedFolders()
85+
{
86+
HRESULT hr = default;
87+
88+
// Get IShellItem of the shell folder
89+
var shellItemIid = typeof(IShellItem).GUID;
90+
using ComPtr<IShellItem> pFolderShellItem = default;
91+
fixed (char* pszFolderShellPath = "Shell:::{3936E9E4-D92C-4EEE-A85A-BC16D5EA0819}")
92+
hr = PInvoke.SHCreateItemFromParsingName(pszFolderShellPath, null, &shellItemIid, (void**)pFolderShellItem.GetAddressOf());
93+
94+
// Get IEnumShellItems of the quick access shell folder
95+
var enumItemsBHID = PInvoke.BHID_EnumItems;
96+
Guid enumShellItemIid = typeof(IEnumShellItems).GUID;
97+
using ComPtr<IEnumShellItems> pEnumShellItems = default;
98+
hr = pFolderShellItem.Get()->BindToHandler(null, &enumItemsBHID, &enumShellItemIid, (void**)pEnumShellItems.GetAddressOf());
99+
100+
// Enumerate pinned folders
101+
int index = 0;
102+
using ComPtr<IShellItem> pShellItem = default;
103+
while (pEnumShellItems.Get()->Next(1, pShellItem.GetAddressOf()) == HRESULT.S_OK)
104+
{
105+
// Get whether the item is pined or not
106+
using ComPtr<IShellItem2> pShellItem2 = pShellItem.As<IShellItem2>(typeof(IShellItem2).GUID);
107+
hr = PInvoke.PSGetPropertyKeyFromName("System.Home.IsPinned", out var propertyKey);
108+
hr = pShellItem2.Get()->GetString(propertyKey, out var szPropertyValue);
109+
if (bool.TryParse(szPropertyValue.ToString(), out var isPinned) && !isPinned)
110+
continue;
127111

128-
//PinnedFoldersChanged?.Invoke(this, eventArgs);
112+
// Get the target path
113+
pShellItem.Get()->GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEEDITING, out var szDisplayName);
114+
var path = szDisplayName.ToString();
115+
PInvoke.CoTaskMemFree(szDisplayName.Value);
129116

130-
return true;
117+
yield return path;
118+
119+
index++;
131120
}
132-
catch
121+
}
122+
123+
async Task<LocationItem> CreateItemOf(string path)
124+
{
125+
var item = await FilesystemTasks.Wrap(() => DriveHelpers.GetRootFromPathAsync(path));
126+
var res = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(path, item));
127+
LocationItem locationItem;
128+
129+
if (string.Equals(path, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase))
133130
{
134-
return false;
131+
locationItem = LocationItem.Create<RecycleBinLocationItem>();
135132
}
133+
else
134+
{
135+
locationItem = LocationItem.Create<LocationItem>();
136+
137+
if (path.Equals(Constants.UserEnvironmentPaths.MyComputerPath, StringComparison.OrdinalIgnoreCase))
138+
locationItem.Text = "ThisPC".GetLocalizedResource();
139+
else if (path.Equals(Constants.UserEnvironmentPaths.NetworkFolderPath, StringComparison.OrdinalIgnoreCase))
140+
locationItem.Text = "Network".GetLocalizedResource();
141+
}
142+
143+
locationItem.Path = path;
144+
locationItem.Section = SectionType.Pinned;
145+
locationItem.MenuOptions = new ContextMenuOptions()
146+
{
147+
IsLocationItem = true,
148+
ShowProperties = true,
149+
ShowUnpinItem = true,
150+
ShowShellItems = true,
151+
ShowEmptyRecycleBin = string.Equals(path, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase)
152+
};
153+
locationItem.IsDefaultLocation = false;
154+
locationItem.Text = res?.Result?.DisplayName ?? Path.GetFileName(path.TrimEnd('\\'));
155+
156+
if (res)
157+
{
158+
locationItem.IsInvalid = false;
159+
if (res.Result is not null)
160+
{
161+
var result = await FileThumbnailHelper.GetIconAsync(
162+
res.Result.Path,
163+
Constants.ShellIconSizes.Small,
164+
true,
165+
IconOptions.ReturnIconOnly | IconOptions.UseCurrentScale);
166+
167+
locationItem.IconData = result;
168+
169+
var bitmapImage = await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => locationItem.IconData.ToBitmapAsync(), Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal);
170+
if (bitmapImage is not null)
171+
locationItem.Icon = bitmapImage;
172+
}
173+
}
174+
else
175+
{
176+
locationItem.Icon = await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => UIHelpers.GetSidebarIconResource(Constants.ImageRes.Folder));
177+
locationItem.IsInvalid = true;
178+
Debug.WriteLine($"Pinned item was invalid {res?.ErrorCode}, item: {path}");
179+
}
180+
181+
return locationItem;
182+
}
183+
184+
NotifyCollectionChangedEventArgs GetChangedActionEventArgs(IReadOnlyList<LocationItem> oldItems, IList<LocationItem> newItems)
185+
{
186+
if (newItems.Count - oldItems.Count is 1)
187+
{
188+
var differences = newItems.Except(oldItems);
189+
if (differences.Take(2).Count() is 1)
190+
return new(NotifyCollectionChangedAction.Add, newItems.First());
191+
}
192+
else if (oldItems.Count - newItems.Count is 1)
193+
{
194+
var differences = oldItems.Except(newItems);
195+
if (differences.Take(2).Count() is 1)
196+
{
197+
for (int i = 0; i < oldItems.Count; i++)
198+
{
199+
if (i >= newItems.Count || !newItems[i].Equals(oldItems[i]))
200+
return new(NotifyCollectionChangedAction.Remove, oldItems[i], index: i);
201+
}
202+
}
203+
}
204+
else if (newItems.Count == oldItems.Count)
205+
{
206+
var differences = oldItems.Except(newItems);
207+
if (differences.Any())
208+
return new(NotifyCollectionChangedAction.Reset);
209+
210+
// First diff from reversed is the designated item
211+
for (int i = oldItems.Count - 1; i >= 0; i--)
212+
{
213+
if (!oldItems[i].Equals(newItems[i]))
214+
return new(NotifyCollectionChangedAction.Move, oldItems[i], index: 0, oldIndex: i);
215+
}
216+
}
217+
218+
return new(NotifyCollectionChangedAction.Reset);
136219
}
137220
}
138221

src/Files.App/ViewModels/MainPageViewModel.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public sealed class MainPageViewModel : ObservableObject
2424
private IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService<IUserSettingsService>();
2525
private IResourcesService ResourcesService { get; } = Ioc.Default.GetRequiredService<IResourcesService>();
2626
private DrivesViewModel DrivesViewModel { get; } = Ioc.Default.GetRequiredService<DrivesViewModel>();
27+
private IQuickAccessService WindowsQuickAccessService = Ioc.Default.GetRequiredService<IQuickAccessService>();
2728

2829
// Properties
2930

@@ -244,7 +245,8 @@ UserSettingsService.GeneralSettingsService.LastSessionTabList is not null &&
244245
await Task.WhenAll(
245246
DrivesViewModel.UpdateDrivesAsync(),
246247
NetworkService.UpdateComputersAsync(),
247-
NetworkService.UpdateShortcutsAsync());
248+
NetworkService.UpdateShortcutsAsync(),
249+
WindowsQuickAccessService.UpdatePinnedFoldersAsync());
248250
}
249251

250252
// Command methods

src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -143,19 +143,45 @@ private async Task UpdateCollectionAsync(NotifyCollectionChangedEventArgs e)
143143
{
144144
case NotifyCollectionChangedAction.Add:
145145
{
146+
if (e.NewItems is not null)
147+
{
148+
// e.NewItems.Cast<LocationItem>().Single()
149+
var cardItem = new WidgetFolderCardItem();
150+
AddItemToCollection(cardItem);
151+
}
146152
}
147153
break;
148154
case NotifyCollectionChangedAction.Move:
149155
{
156+
if (e.OldItems is not null)
157+
{
158+
Items.RemoveAt(e.OldStartingIndex);
159+
160+
// e.NewItems.Cast<LocationItem>().Single()
161+
var cardItem = new WidgetFolderCardItem();
162+
AddItemToCollection(cardItem);
163+
}
150164
}
151165
break;
152166
case NotifyCollectionChangedAction.Remove:
153167
{
168+
if (e.OldItems is not null)
169+
Items.RemoveAt(e.OldStartingIndex);
154170
}
155171
break;
156172
// case NotifyCollectionChangedAction.Reset:
157173
default:
158174
{
175+
var items = QuickAccessService.PinnedFolders;
176+
if (!items.SequenceEqual(Items))
177+
{
178+
Items.Clear();
179+
foreach (var item in items)
180+
{
181+
var cardItem = new WidgetFolderCardItem();
182+
AddItemToCollection(cardItem);
183+
}
184+
}
159185
}
160186
break;
161187
}
@@ -171,19 +197,14 @@ private async Task UpdateCollectionAsync(NotifyCollectionChangedEventArgs e)
171197

172198
bool AddItemToCollection(WidgetFolderCardItem? item, int index = -1)
173199
{
174-
if (item is null)
200+
if (item is null || Items.Any(x => x.Equals(item)))
175201
return false;
176202

177-
if (!Items.Any(x => x.Equals(item)))
178-
{
179-
Items.Insert(index < 0 ? Items.Count : Math.Min(index, Items.Count), item);
180-
_ = item.LoadCardThumbnailAsync()
181-
.ContinueWith(t => App.Logger.LogWarning(t.Exception, null), TaskContinuationOptions.OnlyOnFaulted);
182-
183-
return true;
184-
}
203+
Items.Insert(index < 0 ? Items.Count : Math.Min(index, Items.Count), item);
204+
_ = item.LoadCardThumbnailAsync()
205+
.ContinueWith(t => App.Logger.LogWarning(t.Exception, null), TaskContinuationOptions.OnlyOnFaulted);
185206

186-
return false;
207+
return true;
187208
}
188209
}
189210

0 commit comments

Comments
 (0)