diff --git a/src/Files.App/Data/Enums/IconOptions.cs b/src/Files.App/Data/Enums/IconOptions.cs
index 0f4298637668..3fdba1d05a27 100644
--- a/src/Files.App/Data/Enums/IconOptions.cs
+++ b/src/Files.App/Data/Enums/IconOptions.cs
@@ -19,19 +19,34 @@ public enum IconOptions
///
UseCurrentScale = 1,
+ ///
+ /// Scale the thumbnail to the requested size.
+ ///
+ ResizeThumbnail = 2,
+
///
/// Retrieve only the file icon, even a thumbnail is available. This has the best performance.
///
- ReturnIconOnly = 2,
+ ReturnIconOnly = 4,
///
/// Retrieve only the thumbnail.
///
- ReturnThumbnailOnly = 4,
+ ReturnThumbnailOnly = 8,
///
/// Retrieve a thumbnail only if it is cached or embedded in the file.
///
- ReturnOnlyIfCached = 8,
+ ReturnOnlyIfCached = 16,
+
+ ///
+ /// Default. Retrieve a thumbnail to display a preview of any single item (like a file, folder, or file group).
+ ///
+ SingleItem = 32,
+
+ ///
+ /// Retrieve a thumbnail to display previews of files (or other items) in a list.
+ ///
+ ListView = 64,
}
}
diff --git a/src/Files.App/Data/Items/DriveItem.cs b/src/Files.App/Data/Items/DriveItem.cs
index 6c72c3769c41..0d9086d1b8d9 100644
--- a/src/Files.App/Data/Items/DriveItem.cs
+++ b/src/Files.App/Data/Items/DriveItem.cs
@@ -6,15 +6,15 @@
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Imaging;
using Windows.Storage;
-using Windows.Storage.Streams;
+using Windows.Storage.FileProperties;
using ByteSize = ByteSizeLib.ByteSize;
namespace Files.App.Data.Items
{
public sealed class DriveItem : ObservableObject, INavigationControlItem, ILocatableFolder
{
- private BitmapImage icon;
- public BitmapImage Icon
+ private BitmapImage? icon;
+ public BitmapImage? Icon
{
get => icon;
set
@@ -24,7 +24,7 @@ public BitmapImage Icon
}
}
- public byte[] IconData { get; set; }
+ public byte[]? IconData { get; set; }
private string path;
public string Path
@@ -232,12 +232,11 @@ private void ItemDecorator_Click(object sender, RoutedEventArgs e)
DriveHelpers.EjectDeviceAsync(Path);
}
- public static async Task CreateFromPropertiesAsync(StorageFolder root, string deviceId, string label, DriveType type, IRandomAccessStream imageStream = null)
+ public static async Task CreateFromPropertiesAsync(StorageFolder root, string deviceId, string label, DriveType type, byte[]? imageData = null)
{
var item = new DriveItem();
- if (imageStream is not null)
- item.IconData = await imageStream.ToByteArrayAsync();
+ item.IconData = imageData;
item.Text = type switch
{
@@ -336,8 +335,7 @@ public async Task LoadThumbnailAsync()
if (Root is not null)
{
- using var thumbnail = await DriveHelpers.GetThumbnailAsync(Root);
- IconData ??= thumbnail is not null ? await thumbnail.ToByteArrayAsync() : null;
+ IconData ??= await FileThumbnailHelper.GetIconAsync(Root, 40, ThumbnailMode.SingleItem, ThumbnailOptions.UseCurrentScale);
}
if (string.Equals(DeviceID, "network-folder"))
diff --git a/src/Files.App/Data/Items/WidgetDriveCardItem.cs b/src/Files.App/Data/Items/WidgetDriveCardItem.cs
index 08e3d1136831..06efcae63910 100644
--- a/src/Files.App/Data/Items/WidgetDriveCardItem.cs
+++ b/src/Files.App/Data/Items/WidgetDriveCardItem.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using Microsoft.UI.Xaml.Media.Imaging;
+using Windows.Storage.FileProperties;
namespace Files.App.Data.Items
{
@@ -34,8 +35,7 @@ public async Task LoadCardThumbnailAsync()
if (result is null)
{
- using var thumbnail = await DriveHelpers.GetThumbnailAsync(Item.Root);
- result ??= await thumbnail.ToByteArrayAsync();
+ result ??= await FileThumbnailHelper.GetIconAsync(Item.Root, 40, ThumbnailMode.SingleItem, ThumbnailOptions.UseCurrentScale);
}
thumbnailData = result;
diff --git a/src/Files.App/Data/Models/PinnedFoldersManager.cs b/src/Files.App/Data/Models/PinnedFoldersManager.cs
index d2e84d1cd57a..4fb9036342e8 100644
--- a/src/Files.App/Data/Models/PinnedFoldersManager.cs
+++ b/src/Files.App/Data/Models/PinnedFoldersManager.cs
@@ -108,6 +108,7 @@ public async Task CreateLocationItemFromPathAsync(string path)
{
var result = await FileThumbnailHelper.GetIconAsync(
res.Result.Path,
+ res.Result,
Constants.ShellIconSizes.Small,
true,
IconOptions.ReturnIconOnly | IconOptions.UseCurrentScale);
diff --git a/src/Files.App/Services/Storage/StorageDevicesService.cs b/src/Files.App/Services/Storage/StorageDevicesService.cs
index a13ab8d41a42..a336853c05a9 100644
--- a/src/Files.App/Services/Storage/StorageDevicesService.cs
+++ b/src/Files.App/Services/Storage/StorageDevicesService.cs
@@ -6,6 +6,7 @@
using Microsoft.Extensions.Logging;
using System.IO;
using Windows.Storage;
+using Windows.Storage.FileProperties;
namespace Files.App.Services
{
@@ -42,7 +43,7 @@ public async IAsyncEnumerable GetDrivesAsync()
continue;
}
- using var thumbnail = await DriveHelpers.GetThumbnailAsync(res.Result);
+ var thumbnail = await FileThumbnailHelper.GetIconAsync(res.Result, 40, ThumbnailMode.SingleItem, ThumbnailOptions.UseCurrentScale);
var type = DriveHelpers.GetDriveType(drive);
var label = DriveHelpers.GetExtendedDriveLabel(drive);
var driveItem = await DriveItem.CreateFromPropertiesAsync(res.Result, drive.Name.TrimEnd('\\'), label, type, thumbnail);
diff --git a/src/Files.App/Utils/Storage/Helpers/DriveHelpers.cs b/src/Files.App/Utils/Storage/Helpers/DriveHelpers.cs
index 1a16a5351ecb..61a8d16f0b31 100644
--- a/src/Files.App/Utils/Storage/Helpers/DriveHelpers.cs
+++ b/src/Files.App/Utils/Storage/Helpers/DriveHelpers.cs
@@ -163,10 +163,5 @@ public static unsafe string GetExtendedDriveLabel(SystemIO.DriveInfo drive)
}) ?? "";
}
-
- public static async Task GetThumbnailAsync(StorageFolder folder)
- => (StorageItemThumbnail)await FilesystemTasks.Wrap(()
- => folder.GetThumbnailAsync(ThumbnailMode.SingleItem, 40, ThumbnailOptions.UseCurrentScale).AsTask()
- );
}
}
\ No newline at end of file
diff --git a/src/Files.App/Utils/Storage/Helpers/FileThumbnailHelper.cs b/src/Files.App/Utils/Storage/Helpers/FileThumbnailHelper.cs
index 72a838089f47..d037ebdd3a44 100644
--- a/src/Files.App/Utils/Storage/Helpers/FileThumbnailHelper.cs
+++ b/src/Files.App/Utils/Storage/Helpers/FileThumbnailHelper.cs
@@ -1,20 +1,58 @@
// Copyright (c) Files Community
// Licensed under the MIT License.
+using Windows.Storage;
using Windows.Storage.FileProperties;
namespace Files.App.Utils.Storage
{
public static class FileThumbnailHelper
{
+ public static async Task GetIconAsync(string? path, IStorageItem? item, uint requestedSize, bool isFolder, IconOptions iconOptions)
+ {
+ byte[]? result = null;
+ if (path is not null)
+ result ??= await GetIconAsync(path, requestedSize, isFolder, iconOptions);
+ if (item is not null)
+ result ??= await GetIconAsync(item, requestedSize, iconOptions);
+ return result;
+ }
+
///
/// Returns icon or thumbnail for given file or folder
///
- public static async Task GetIconAsync(string path, uint requestedSize, bool isFolder, IconOptions iconOptions)
+ public static Task GetIconAsync(string path, uint requestedSize, bool isFolder, IconOptions iconOptions)
{
var size = iconOptions.HasFlag(IconOptions.UseCurrentScale) ? requestedSize * App.AppModel.AppWindowDPI : requestedSize;
- return await Win32Helper.StartSTATask(() => Win32Helper.GetIcon(path, (int)size, isFolder, iconOptions));
+ return Win32Helper.StartSTATask(() => Win32Helper.GetIcon(path, (int)size, isFolder, iconOptions));
+ }
+
+ ///
+ /// Returns thumbnail for given file or folder using Storage API
+ ///
+ public static Task GetIconAsync(IStorageItem item, uint requestedSize, IconOptions iconOptions)
+ {
+ var thumbnailOptions = (iconOptions.HasFlag(IconOptions.UseCurrentScale) ? ThumbnailOptions.UseCurrentScale : 0) |
+ (iconOptions.HasFlag(IconOptions.ReturnOnlyIfCached) ? ThumbnailOptions.ReturnOnlyIfCached : 0) |
+ (iconOptions.HasFlag(IconOptions.ResizeThumbnail) ? ThumbnailOptions.ResizeThumbnail : 0);
+
+ var thumbnailMode = iconOptions.HasFlag(IconOptions.ListView) ? ThumbnailMode.ListView : ThumbnailMode.SingleItem;
+
+ return GetIconAsync(item, requestedSize, thumbnailMode, thumbnailOptions);
+ }
+
+ public static async Task GetIconAsync(IStorageItem item, uint requestedSize, ThumbnailMode thumbnailMode, ThumbnailOptions thumbnailOptions)
+ {
+ using StorageItemThumbnail thumbnail = item switch
+ {
+ BaseStorageFile file => await FilesystemTasks.Wrap(() => file.GetThumbnailAsync(thumbnailMode, requestedSize, thumbnailOptions).AsTask()),
+ BaseStorageFolder folder => await FilesystemTasks.Wrap(() => folder.GetThumbnailAsync(thumbnailMode, requestedSize, thumbnailOptions).AsTask()),
+ _ => new(null!, FileSystemStatusCode.Generic)
+ };
+ if (thumbnail is not null && thumbnail.Size != 0 && thumbnail.OriginalHeight != 0 && thumbnail.OriginalWidth != 0)
+ return await thumbnail.ToByteArrayAsync();
+ return null;
}
///
@@ -23,14 +61,13 @@ public static class FileThumbnailHelper
///
///
///
- public static async Task GetIconOverlayAsync(string path, bool isFolder)
- => await Win32Helper.StartSTATask(() => Win32Helper.GetIconOverlay(path, isFolder));
+ public static Task GetIconOverlayAsync(string path, bool isFolder)
+ => Win32Helper.StartSTATask(() => Win32Helper.GetIconOverlay(path, isFolder));
[Obsolete]
- public static async Task LoadIconFromPathAsync(string filePath, uint thumbnailSize, ThumbnailMode thumbnailMode, ThumbnailOptions thumbnailOptions, bool isFolder = false)
+ public static Task LoadIconFromPathAsync(string filePath, uint thumbnailSize, ThumbnailMode thumbnailMode, ThumbnailOptions thumbnailOptions, bool isFolder = false)
{
- var result = await GetIconAsync(filePath, thumbnailSize, isFolder, IconOptions.None);
- return result;
+ return GetIconAsync(filePath, thumbnailSize, isFolder, IconOptions.None);
}
}
}
\ No newline at end of file
diff --git a/src/Files.App/Utils/Storage/Search/FolderSearch.cs b/src/Files.App/Utils/Storage/Search/FolderSearch.cs
index 08df6e230def..940869a0abe8 100644
--- a/src/Files.App/Utils/Storage/Search/FolderSearch.cs
+++ b/src/Files.App/Utils/Storage/Search/FolderSearch.cs
@@ -512,6 +512,7 @@ private async Task GetListedItemAsync(IStorageItem item)
{
var iconResult = await FileThumbnailHelper.GetIconAsync(
item.Path,
+ item,
Constants.ShellIconSizes.Small,
item.IsOfType(StorageItemTypes.Folder),
IconOptions.ReturnIconOnly | IconOptions.UseCurrentScale);
diff --git a/src/Files.App/ViewModels/Properties/Items/DriveProperties.cs b/src/Files.App/ViewModels/Properties/Items/DriveProperties.cs
index 750ac63b8ed0..8fc2c45c5c07 100644
--- a/src/Files.App/ViewModels/Properties/Items/DriveProperties.cs
+++ b/src/Files.App/ViewModels/Properties/Items/DriveProperties.cs
@@ -52,6 +52,7 @@ public async override Task GetSpecialPropertiesAsync()
{
var result = await FileThumbnailHelper.GetIconAsync(
Drive.Path,
+ diskRoot,
Constants.ShellIconSizes.ExtraLarge,
true,
IconOptions.ReturnIconOnly | IconOptions.UseCurrentScale);
diff --git a/src/Files.App/ViewModels/Properties/Items/FileProperties.cs b/src/Files.App/ViewModels/Properties/Items/FileProperties.cs
index d0de82a45d27..1d46b316b464 100644
--- a/src/Files.App/ViewModels/Properties/Items/FileProperties.cs
+++ b/src/Files.App/ViewModels/Properties/Items/FileProperties.cs
@@ -108,8 +108,13 @@ public override async Task GetSpecialPropertiesAsync()
ViewModel.ItemSizeOnDisk = Win32Helper.GetFileSizeOnDisk(Item.ItemPath)?.ToLongSizeString() ??
string.Empty;
+ string filePath = (Item as ShortcutItem)?.TargetPath ?? Item.ItemPath;
+ BaseStorageFile? file = !string.IsNullOrWhiteSpace(filePath) ?
+ await AppInstance.ShellViewModel.GetFileFromPathAsync(filePath) : null!;
+
var result = await FileThumbnailHelper.GetIconAsync(
Item.ItemPath,
+ file,
Constants.ShellIconSizes.ExtraLarge,
false,
IconOptions.UseCurrentScale);
@@ -132,9 +137,6 @@ public override async Task GetSpecialPropertiesAsync()
}
}
- string filePath = (Item as ShortcutItem)?.TargetPath ?? Item.ItemPath;
- BaseStorageFile file = await AppInstance.ShellViewModel.GetFileFromPathAsync(filePath);
-
// Couldn't access the file and can't load any other properties
if (file is null)
return;
@@ -152,7 +154,7 @@ public override async Task GetSpecialPropertiesAsync()
ViewModel.UncompressedItemSizeBytes = uncompressedSize;
}
- if (file.Properties is not null)
+ if (file?.Properties is not null)
GetOtherPropertiesAsync(file.Properties);
}
diff --git a/src/Files.App/ViewModels/Properties/Items/FolderProperties.cs b/src/Files.App/ViewModels/Properties/Items/FolderProperties.cs
index 0dd47ef2610a..eec92284556c 100644
--- a/src/Files.App/ViewModels/Properties/Items/FolderProperties.cs
+++ b/src/Files.App/ViewModels/Properties/Items/FolderProperties.cs
@@ -79,8 +79,13 @@ public async override Task GetSpecialPropertiesAsync()
ViewModel.CanCompressContent = Win32Helper.CanCompressContent(Item.ItemPath);
ViewModel.IsContentCompressed = Win32Helper.HasFileAttribute(Item.ItemPath, System.IO.FileAttributes.Compressed);
+ string folderPath = (Item as ShortcutItem)?.TargetPath ?? Item.ItemPath;
+ BaseStorageFolder? storageFolder = !string.IsNullOrWhiteSpace(folderPath) ?
+ await AppInstance.ShellViewModel.GetFolderFromPathAsync(folderPath) : null!;
+
var result = await FileThumbnailHelper.GetIconAsync(
Item.ItemPath,
+ storageFolder,
Constants.ShellIconSizes.ExtraLarge,
true,
IconOptions.UseCurrentScale);
@@ -111,9 +116,6 @@ public async override Task GetSpecialPropertiesAsync()
}
}
- string folderPath = (Item as ShortcutItem)?.TargetPath ?? Item.ItemPath;
- BaseStorageFolder storageFolder = await AppInstance.ShellViewModel.GetFolderFromPathAsync(folderPath);
-
if (storageFolder is not null)
{
ViewModel.ItemCreatedTimestampReal = storageFolder.DateCreated;
diff --git a/src/Files.App/ViewModels/ShellViewModel.cs b/src/Files.App/ViewModels/ShellViewModel.cs
index 3538ce53f269..1fb7eac3e899 100644
--- a/src/Files.App/ViewModels/ShellViewModel.cs
+++ b/src/Files.App/ViewModels/ShellViewModel.cs
@@ -973,7 +973,7 @@ private async Task GetShieldIcon()
return shieldIcon;
}
- private async Task LoadThumbnailAsync(ListedItem item, CancellationToken cancellationToken)
+ private async Task LoadThumbnailAsync(ListedItem item, CancellationToken cancellationToken)
{
var loadNonCachedThumbnail = false;
var thumbnailSize = LayoutSizeKindHelper.GetIconSize(folderSettings.LayoutMode);
@@ -1086,6 +1086,42 @@ await dispatcherQueue.EnqueueOrInvokeAsync(async () =>
}
}, cancellationToken);
}
+
+ return result is not null;
+ }
+
+ private async Task LoadThumbnailAsync(ListedItem item, IStorageItem matchingStorageItem, CancellationToken cancellationToken)
+ {
+ var thumbnailSize = LayoutSizeKindHelper.GetIconSize(folderSettings.LayoutMode);
+ // SingleItem returns image thumbnails in the correct aspect ratio for the grid layouts
+ // ListView is used for the details and columns layout
+ // We use ReturnOnlyIfCached because otherwise folders thumbnails have a black background, this has the downside the folder previews don't work
+ var iconOptions = matchingStorageItem switch
+ {
+ BaseStorageFolder => IconOptions.SingleItem | IconOptions.ReturnOnlyIfCached,
+ BaseStorageFile when thumbnailSize < 96 => IconOptions.ListView | IconOptions.ResizeThumbnail,
+ _ => IconOptions.SingleItem | IconOptions.ResizeThumbnail,
+ };
+
+ var result = await FileThumbnailHelper.GetIconAsync(
+ matchingStorageItem,
+ thumbnailSize,
+ iconOptions);
+
+ if (result is not null)
+ {
+ await dispatcherQueue.EnqueueOrInvokeAsync(async () =>
+ {
+ // Assign FileImage property
+ var image = await result.ToBitmapAsync();
+ if (image is not null)
+ item.FileImage = image;
+ }, Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal);
+
+ return true;
+ }
+
+ return false;
}
private static void SetFileTag(ListedItem item)
@@ -1128,7 +1164,7 @@ public async Task LoadExtendedItemPropertiesAsync(ListedItem item)
}
cts.Token.ThrowIfCancellationRequested();
- await LoadThumbnailAsync(item, cts.Token);
+ var wasThumbnailLoaded = await LoadThumbnailAsync(item, cts.Token);
cts.Token.ThrowIfCancellationRequested();
if (item.IsLibrary || item.PrimaryItemAttribute == StorageItemTypes.File || item.IsArchive)
@@ -1180,6 +1216,10 @@ await dispatcherQueue.EnqueueOrInvokeAsync(() =>
},
Microsoft.UI.Dispatching.DispatcherQueuePriority.Low);
+ // For MTP devices load thumbnail using Storage API (#15084)
+ if (!wasThumbnailLoaded)
+ await LoadThumbnailAsync(item, matchingStorageFile, cts.Token);
+
SetFileTag(item);
wasSyncStatusLoaded = true;
}
@@ -1250,6 +1290,10 @@ await dispatcherQueue.EnqueueOrInvokeAsync(() =>
},
Microsoft.UI.Dispatching.DispatcherQueuePriority.Low);
+ // For MTP devices load thumbnail using Storage API (#15084)
+ if (!wasThumbnailLoaded)
+ await LoadThumbnailAsync(item, matchingStorageFolder, cts.Token);
+
SetFileTag(item);
wasSyncStatusLoaded = true;
}
diff --git a/src/Files.App/ViewModels/UserControls/Previews/BasePreviewModel.cs b/src/Files.App/ViewModels/UserControls/Previews/BasePreviewModel.cs
index 28f3a26cefd6..e42111fefa22 100644
--- a/src/Files.App/ViewModels/UserControls/Previews/BasePreviewModel.cs
+++ b/src/Files.App/ViewModels/UserControls/Previews/BasePreviewModel.cs
@@ -86,6 +86,7 @@ public async virtual Task> LoadPreviewAndDetailsAsync()
{
var result = await FileThumbnailHelper.GetIconAsync(
Item.ItemPath,
+ Item.ItemFile,
Constants.ShellIconSizes.Jumbo,
false,
IconOptions.None);
diff --git a/src/Files.App/ViewModels/UserControls/Previews/FolderPreviewViewModel.cs b/src/Files.App/ViewModels/UserControls/Previews/FolderPreviewViewModel.cs
index f4a2d746a3d1..20be89f82dae 100644
--- a/src/Files.App/ViewModels/UserControls/Previews/FolderPreviewViewModel.cs
+++ b/src/Files.App/ViewModels/UserControls/Previews/FolderPreviewViewModel.cs
@@ -29,6 +29,7 @@ private async Task LoadPreviewAndDetailsAsync()
var result = await FileThumbnailHelper.GetIconAsync(
Item.ItemPath,
+ Folder,
Constants.ShellIconSizes.Jumbo,
true,
IconOptions.None);