IconProvider, container exceptions, refactor
This commit is contained in:
@@ -120,6 +120,8 @@
|
||||
<converters:ItemToImageConverter x:Key="ItemToImageConverter"/>
|
||||
<converters:IsNullConverter x:Key="IsNullConverter"/>
|
||||
<converters:IsNullConverter x:Key="IsNotNullConverter" Inverse="true"/>
|
||||
<converters:IsEmptyConverter x:Key="IsEmptyConverter"/>
|
||||
<converters:IsEmptyConverter x:Key="IsNotEmptyConverter" Inverse="true"/>
|
||||
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
@@ -132,7 +134,7 @@
|
||||
<Setter Property="FontSize" Value="16"/>
|
||||
</Style>
|
||||
<Style Selector="TextBlock.SmallText">
|
||||
<Setter Property="FontSize" Value="16"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
</Style>
|
||||
<Style Selector="ListBox.ContentListView">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
@@ -145,5 +147,13 @@
|
||||
<ContextMenu Items="{Binding Converter={StaticResource ContextMenuGenerator}}"/>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.PlacesItem">
|
||||
<Setter Property="Background" Value="#01000000"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.PlacesItem:pointerover">
|
||||
<Setter Property="Background" Value="{DynamicResource AppBackgroundColor}"/>
|
||||
</Style>
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
|
||||
23
src/GuiApp/FileTime.Avalonia/Converters/IsEmptyConverter.cs
Normal file
23
src/GuiApp/FileTime.Avalonia/Converters/IsEmptyConverter.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace FileTime.Avalonia.Converters
|
||||
{
|
||||
public class IsEmptyConverter : IValueConverter
|
||||
{
|
||||
public bool Inverse { get; set; }
|
||||
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
var result = value is string s && string.IsNullOrWhiteSpace(s);
|
||||
if (Inverse) result = !result;
|
||||
return result;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,26 +5,45 @@ using Avalonia.Svg.Skia;
|
||||
using FileTime.Avalonia.IconProviders;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using FileTime.Core.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FileTime.Avalonia.Converters
|
||||
{
|
||||
public class ItemToImageConverter : IValueConverter
|
||||
{
|
||||
private readonly IIconProvider _iconProvider;
|
||||
|
||||
public ItemToImageConverter()
|
||||
{
|
||||
_iconProvider = App.ServiceProvider.GetService<IIconProvider>()!;
|
||||
}
|
||||
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value == null) return null;
|
||||
|
||||
IIconProvider converter = new MaterialIconProvider();
|
||||
|
||||
IItem item = value switch
|
||||
{
|
||||
ContainerViewModel container => container.Container,
|
||||
ElementViewModel element => element.Element,
|
||||
IItem i => i,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
var path = converter.GetImage(item)!;
|
||||
var source = SvgSource.Load<SvgSource>("avares://FileTime.Avalonia" + path, null);
|
||||
SvgSource? source;
|
||||
var path = _iconProvider.GetImage(item)!;
|
||||
if (path.Type == Models.ImagePathType.Absolute)
|
||||
{
|
||||
source = SvgSource.Load<SvgSource>(path.Path!, null);
|
||||
}
|
||||
else if(path.Type == Models.ImagePathType.Raw)
|
||||
{
|
||||
return path.Image;
|
||||
}
|
||||
else
|
||||
{
|
||||
source = SvgSource.Load<SvgSource>("avares://FileTime.Avalonia" + path.Path, null);
|
||||
}
|
||||
return new SvgImage { Source = source };
|
||||
}
|
||||
|
||||
|
||||
@@ -11,16 +11,17 @@
|
||||
<None Remove=".gitignore" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="0.10.11" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.11" />
|
||||
<PackageReference Include="Avalonia" Version="0.10.12" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.12" />
|
||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.11" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.11" />
|
||||
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.11.1" />
|
||||
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.11.5" />
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.12" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.12" />
|
||||
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.12" />
|
||||
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.12" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||
<PackageReference Include="MvvmGen" Version="1.1.2" />
|
||||
<PackageReference Include="Syroot.Windows.IO.KnownFolders" Version="1.2.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\AppCommon\FileTime.App.Core\FileTime.App.Core.csproj" />
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using FileTime.Avalonia.Models;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Avalonia.IconProviders
|
||||
{
|
||||
public interface IIconProvider
|
||||
{
|
||||
string GetImage(IItem item);
|
||||
ImagePath GetImage(IItem item);
|
||||
bool EnableAdvancedIcons { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,75 @@
|
||||
using Avalonia.Media.Imaging;
|
||||
using FileTime.Avalonia.Misc;
|
||||
using FileTime.Avalonia.Models;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Providers.Local;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace FileTime.Avalonia.IconProviders
|
||||
{
|
||||
public class MaterialIconProvider : IIconProvider
|
||||
{
|
||||
public string GetImage(IItem item)
|
||||
public bool EnableAdvancedIcons { get; set; } = true;
|
||||
|
||||
public ImagePath GetImage(IItem item)
|
||||
{
|
||||
var icon = "file.svg";
|
||||
if (item is IContainer)
|
||||
var icon = item is IContainer ? "folder.svg" : "file.svg";
|
||||
|
||||
if (EnableAdvancedIcons)
|
||||
{
|
||||
icon = "folder.svg";
|
||||
}
|
||||
else if (item is IElement element)
|
||||
{
|
||||
if(element is LocalFile localFile && element.FullName.EndsWith(".svg"))
|
||||
if (item is IElement element)
|
||||
{
|
||||
return localFile.File.FullName;
|
||||
}
|
||||
icon = !element.Name.Contains('.')
|
||||
? icon
|
||||
: element.Name.Split('.').Last() switch
|
||||
if (element is LocalFile localFile && (element.FullName?.EndsWith(".svg") ?? false))
|
||||
{
|
||||
"cs" => "csharp.svg",
|
||||
_ => icon
|
||||
};
|
||||
return new ImagePath(ImagePathType.Absolute, localFile.File.FullName);
|
||||
}
|
||||
icon = !element.Name.Contains('.')
|
||||
? icon
|
||||
: element.Name.Split('.').Last() switch
|
||||
{
|
||||
"cs" => "csharp.svg",
|
||||
_ => icon
|
||||
};
|
||||
}
|
||||
/*else if (item is LocalFolder folder && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
var file = new FileInfo(Path.Combine(folder.FullName, "desktop.ini"));
|
||||
if (file.Exists)
|
||||
{
|
||||
var lines = File.ReadAllLines(file.FullName);
|
||||
if (Array.Find(lines, l => l.StartsWith("iconresource", StringComparison.OrdinalIgnoreCase)) is string iconLine)
|
||||
{
|
||||
var nameLineValue = string.Join('=', iconLine.Split('=')[1..]);
|
||||
var environemntVariables = Environment.GetEnvironmentVariables();
|
||||
foreach (var keyo in environemntVariables.Keys)
|
||||
{
|
||||
if (keyo is string key && environemntVariables[key] is string value)
|
||||
{
|
||||
nameLineValue = nameLineValue.Replace($"%{key}%", value);
|
||||
}
|
||||
}
|
||||
|
||||
var parts = nameLineValue.Split(',');
|
||||
if (parts.Length >= 2 && long.TryParse(parts[^1], out var parsedResourceId))
|
||||
{
|
||||
if (parsedResourceId < 0) parsedResourceId *= -1;
|
||||
|
||||
var extractedIcon = NativeMethodHelpers.GetIconResource(string.Join(',', parts[..^1]), (uint)parsedResourceId);
|
||||
|
||||
var extractedIconAsStream = new MemoryStream();
|
||||
extractedIcon.Save(extractedIconAsStream);
|
||||
extractedIconAsStream.Position = 0;
|
||||
|
||||
return new ImagePath(ImagePathType.Raw, new Bitmap(extractedIconAsStream));
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
return "/Assets/material/" + icon;
|
||||
return new ImagePath(ImagePathType.Asset, "/Assets/material/" + icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.IO;
|
||||
using Avalonia.Media.Imaging;
|
||||
using FileTime.Avalonia.Misc;
|
||||
using FileTime.Avalonia.Models;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Providers.Local;
|
||||
|
||||
namespace FileTime.Avalonia.IconProviders
|
||||
{
|
||||
public class SystemIconProvider : IIconProvider
|
||||
{
|
||||
public bool EnableAdvancedIcons { get; set; }
|
||||
|
||||
public ImagePath GetImage(IItem item)
|
||||
{
|
||||
if (item is LocalFile file)
|
||||
{
|
||||
var extractedIconAsStream = new MemoryStream();
|
||||
var extractedIcon = System.Drawing.Icon.ExtractAssociatedIcon(file.File.FullName);
|
||||
extractedIcon.Save(extractedIconAsStream);
|
||||
extractedIconAsStream.Position = 0;
|
||||
return new ImagePath(ImagePathType.Raw, new Bitmap(extractedIconAsStream));
|
||||
}
|
||||
|
||||
var icon = item is IContainer ? "folder.svg" : "file.svg";
|
||||
return new ImagePath(ImagePathType.Asset, "/Assets/material/" + icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
73
src/GuiApp/FileTime.Avalonia/Misc/WindowsNativeMethods.cs
Normal file
73
src/GuiApp/FileTime.Avalonia/Misc/WindowsNativeMethods.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace FileTime.Avalonia.Misc
|
||||
{
|
||||
public static class WindowsNativeMethods
|
||||
{
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
|
||||
internal static extern IntPtr LoadLibrary(string lpLibFileName);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
|
||||
internal static extern int LoadString(IntPtr hInstance, uint wID, StringBuilder lpBuffer, int nBufferMax);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr LoadIcon(IntPtr hInstance, IntPtr lpIconName);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern int FreeLibrary(IntPtr hLibModule);
|
||||
|
||||
[DllImport("shell32.dll")]
|
||||
public static extern IntPtr ExtractAssociatedIcon(IntPtr hInst, StringBuilder lpIconPath, out ushort lpiIcon);
|
||||
}
|
||||
|
||||
public static class NativeMethodHelpers
|
||||
{
|
||||
public static string GetStringResource(string fileName, uint resourceId)
|
||||
{
|
||||
IntPtr? handle = null;
|
||||
try
|
||||
{
|
||||
handle = WindowsNativeMethods.LoadLibrary(fileName);
|
||||
StringBuilder buffer = new(8192); //Buffer for output from LoadString()
|
||||
int length = WindowsNativeMethods.LoadString(handle.Value, resourceId, buffer, buffer.Capacity);
|
||||
return buffer.ToString(0, length); //Return the part of the buffer that was used.
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (handle is IntPtr validHandle)
|
||||
{
|
||||
WindowsNativeMethods.FreeLibrary(validHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Icon GetIconResource(string fileName, uint resourceId)
|
||||
{
|
||||
IntPtr? handle = null;
|
||||
try
|
||||
{
|
||||
handle = WindowsNativeMethods.LoadLibrary(fileName);
|
||||
IntPtr handle2 = WindowsNativeMethods.LoadIcon(handle.Value, new IntPtr(resourceId));
|
||||
return Icon.FromHandle(handle2);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (handle is IntPtr validHandle)
|
||||
{
|
||||
WindowsNativeMethods.FreeLibrary(validHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*public static Icon GetAssociatedIcon()
|
||||
{
|
||||
ushort uicon;
|
||||
StringBuilder strB = new StringBuilder(fileName);
|
||||
IntPtr handle = WindowsNativeMethods.ExtractAssociatedIcon(this.Handle, strB, out uicon);
|
||||
return Icon.FromHandle(handle);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
21
src/GuiApp/FileTime.Avalonia/Models/ImagePath.cs
Normal file
21
src/GuiApp/FileTime.Avalonia/Models/ImagePath.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace FileTime.Avalonia.Models
|
||||
{
|
||||
public class ImagePath
|
||||
{
|
||||
public string? Path { get; }
|
||||
public ImagePathType Type { get; }
|
||||
public object? Image { get; }
|
||||
|
||||
public ImagePath(ImagePathType type, string path)
|
||||
{
|
||||
Path = path;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public ImagePath(ImagePathType type, object image)
|
||||
{
|
||||
Image = image;
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/GuiApp/FileTime.Avalonia/Models/ImagePathType.cs
Normal file
9
src/GuiApp/FileTime.Avalonia/Models/ImagePathType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace FileTime.Avalonia.Models
|
||||
{
|
||||
public enum ImagePathType
|
||||
{
|
||||
Asset,
|
||||
Absolute,
|
||||
Raw
|
||||
}
|
||||
}
|
||||
16
src/GuiApp/FileTime.Avalonia/Models/PlaceInfo.cs
Normal file
16
src/GuiApp/FileTime.Avalonia/Models/PlaceInfo.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Avalonia.Models
|
||||
{
|
||||
public class PlaceInfo
|
||||
{
|
||||
public string Name { get; }
|
||||
public IContainer Container { get; }
|
||||
|
||||
public PlaceInfo(string name, IContainer container)
|
||||
{
|
||||
Name = name;
|
||||
Container = container;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,9 @@ namespace FileTime.Avalonia.Models
|
||||
[Property]
|
||||
private string _fullName;
|
||||
|
||||
[Property]
|
||||
private string _label;
|
||||
|
||||
[Property]
|
||||
private long _size;
|
||||
|
||||
@@ -37,11 +40,17 @@ namespace FileTime.Avalonia.Models
|
||||
_driveInfo = driveInfo;
|
||||
_container = container;
|
||||
|
||||
Name = container.Name;
|
||||
FullName = container.FullName;
|
||||
Size = driveInfo.TotalSize;
|
||||
Free = driveInfo.AvailableFreeSpace;
|
||||
Used = driveInfo.TotalSize - driveInfo.AvailableFreeSpace;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void Refresh()
|
||||
{
|
||||
Name = _container.Name;
|
||||
FullName = _container.FullName;
|
||||
Label = _driveInfo.VolumeLabel;
|
||||
Size = _driveInfo.TotalSize;
|
||||
Free = _driveInfo.AvailableFreeSpace;
|
||||
Used = _driveInfo.TotalSize - _driveInfo.AvailableFreeSpace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using FileTime.Avalonia.Misc;
|
||||
|
||||
#pragma warning disable CA1416
|
||||
namespace FileTime.Avalonia.Services
|
||||
@@ -50,7 +51,7 @@ namespace FileTime.Avalonia.Services
|
||||
{
|
||||
if (parsedResourceId < 0) parsedResourceId *= -1;
|
||||
|
||||
text = GetStringResource(parts[0], (uint)parsedResourceId);
|
||||
text = NativeMethodHelpers.GetStringResource(string.Join(',', parts[..^1]), (uint)parsedResourceId);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -231,37 +232,6 @@ namespace FileTime.Avalonia.Services
|
||||
|
||||
return (resultX, resultY);
|
||||
}
|
||||
|
||||
static string GetStringResource(string fileName, uint resourceId)
|
||||
{
|
||||
IntPtr? handle = null;
|
||||
try
|
||||
{
|
||||
handle = NativeMethods.LoadLibrary(fileName);
|
||||
StringBuilder buffer = new(8192); //Buffer for output from LoadString()
|
||||
int length = NativeMethods.LoadString(handle.Value, resourceId, buffer, buffer.Capacity);
|
||||
return buffer.ToString(0, length); //Return the part of the buffer that was used.
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (handle is IntPtr validHandle)
|
||||
{
|
||||
NativeMethods.FreeLibrary(validHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class NativeMethods
|
||||
{
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
|
||||
internal static extern IntPtr LoadLibrary(string lpLibFileName);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
|
||||
internal static extern int LoadString(IntPtr hInstance, uint wID, StringBuilder lpBuffer, int nBufferMax);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern int FreeLibrary(IntPtr hLibModule);
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA1416
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using FileTime.Avalonia.Application;
|
||||
using FileTime.Avalonia.IconProviders;
|
||||
using FileTime.Avalonia.Services;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using FileTime.Core.Command;
|
||||
@@ -21,7 +22,8 @@ namespace FileTime.Avalonia
|
||||
{
|
||||
serviceCollection = serviceCollection
|
||||
.AddLogging()
|
||||
.AddSingleton<ItemNameConverterService>();
|
||||
.AddSingleton<ItemNameConverterService>()
|
||||
.AddSingleton<IIconProvider, MaterialIconProvider>();
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace FileTime.Avalonia.ViewModels
|
||||
private bool _disposed;
|
||||
private bool _isRefreshing;
|
||||
private bool _isInitialized;
|
||||
private INewItemProcessor _newItemProcessor;
|
||||
private readonly INewItemProcessor _newItemProcessor;
|
||||
|
||||
[Property]
|
||||
private IContainer _container;
|
||||
@@ -37,17 +37,19 @@ namespace FileTime.Avalonia.ViewModels
|
||||
[Property]
|
||||
private ContainerViewModel? _parent;
|
||||
|
||||
[Property]
|
||||
private List<Exception> _exceptions;
|
||||
|
||||
public IItem Item => _container;
|
||||
|
||||
private readonly ObservableCollection<ContainerViewModel> _containers = new ObservableCollection<ContainerViewModel>();
|
||||
private ObservableCollection<ContainerViewModel> _containers = new();
|
||||
|
||||
private readonly ObservableCollection<ElementViewModel> _elements = new ObservableCollection<ElementViewModel>();
|
||||
private ObservableCollection<ElementViewModel> _elements = new();
|
||||
|
||||
private readonly ObservableCollection<IItemViewModel> _items = new ObservableCollection<IItemViewModel>();
|
||||
private ObservableCollection<IItemViewModel> _items = new();
|
||||
|
||||
public List<IItemViewModel> ChildrenToAdopt { get; } = new List<IItemViewModel>();
|
||||
|
||||
|
||||
[PropertyInvalidate(nameof(IsSelected))]
|
||||
[PropertyInvalidate(nameof(IsAlternative))]
|
||||
[PropertyInvalidate(nameof(IsMarked))]
|
||||
@@ -64,7 +66,7 @@ namespace FileTime.Avalonia.ViewModels
|
||||
|
||||
public List<ItemNamePart> DisplayName => ItemNameConverterService.GetDisplayName(this);
|
||||
|
||||
[Obsolete]
|
||||
[Obsolete($"This property is for databinding only, use {nameof(GetContainers)} method instead.")]
|
||||
public ObservableCollection<ContainerViewModel> Containers
|
||||
{
|
||||
get
|
||||
@@ -72,9 +74,17 @@ namespace FileTime.Avalonia.ViewModels
|
||||
if (!_isInitialized) Task.Run(Refresh);
|
||||
return _containers;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != _containers)
|
||||
{
|
||||
_containers = value;
|
||||
OnPropertyChanged(nameof(Containers));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
[Obsolete($"This property is for databinding only, use {nameof(GetElements)} method instead.")]
|
||||
public ObservableCollection<ElementViewModel> Elements
|
||||
{
|
||||
get
|
||||
@@ -82,9 +92,16 @@ namespace FileTime.Avalonia.ViewModels
|
||||
if (!_isInitialized) Task.Run(Refresh);
|
||||
return _elements;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != _elements)
|
||||
{
|
||||
_elements = value;
|
||||
OnPropertyChanged(nameof(Elements));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
[Obsolete($"This property is for databinding only, use {nameof(GetItems)} method instead.")]
|
||||
public ObservableCollection<IItemViewModel> Items
|
||||
{
|
||||
get
|
||||
@@ -92,6 +109,14 @@ namespace FileTime.Avalonia.ViewModels
|
||||
if (!_isInitialized) Task.Run(Refresh);
|
||||
return _items;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != _items)
|
||||
{
|
||||
_items = value;
|
||||
OnPropertyChanged(nameof(Items));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ContainerViewModel(INewItemProcessor newItemProcessor, ContainerViewModel? parent, IContainer container, ItemNameConverterService itemNameConverterService) : this(itemNameConverterService)
|
||||
@@ -125,44 +150,41 @@ namespace FileTime.Avalonia.ViewModels
|
||||
|
||||
_isInitialized = true;
|
||||
|
||||
Exceptions = new List<Exception>();
|
||||
try
|
||||
{
|
||||
_isRefreshing = true;
|
||||
|
||||
var containers = (await _container.GetContainers())!.Select(c => AdoptOrReuseOrCreateItem(c, (c2) => new ContainerViewModel(_newItemProcessor, this, c2, ItemNameConverterService))).ToList();
|
||||
var elements = (await _container.GetElements())!.Select(e => AdoptOrReuseOrCreateItem(e, (e2) => new ElementViewModel(e2, this, ItemNameConverterService))).ToList();
|
||||
Exceptions = new List<Exception>(_container.Exceptions);
|
||||
|
||||
var containersToRemove = _containers.Except(containers);
|
||||
|
||||
_containers.Clear();
|
||||
_elements.Clear();
|
||||
_items.Clear();
|
||||
|
||||
foreach (var containerToRemove in containersToRemove)
|
||||
foreach (var containerToRemove in _containers.Except(containers))
|
||||
{
|
||||
containerToRemove?.Dispose();
|
||||
}
|
||||
|
||||
foreach (var container in containers)
|
||||
if (initializeChildren)
|
||||
{
|
||||
if (initializeChildren) await container.Init(false);
|
||||
|
||||
_containers.Add(container);
|
||||
_items.Add(container);
|
||||
}
|
||||
|
||||
foreach (var element in elements)
|
||||
{
|
||||
_elements.Add(element);
|
||||
_items.Add(element);
|
||||
foreach (var container in containers)
|
||||
{
|
||||
await container.Init(false);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < _items.Count; i++)
|
||||
{
|
||||
_items[i].IsAlternative = i % 2 == 1;
|
||||
}
|
||||
|
||||
Containers = new ObservableCollection<ContainerViewModel>(containers);
|
||||
Elements = new ObservableCollection<ElementViewModel>(elements);
|
||||
Items = new ObservableCollection<IItemViewModel>(containers.Cast<IItemViewModel>().Concat(elements));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_exceptions.Add(e);
|
||||
}
|
||||
catch { }
|
||||
|
||||
await _newItemProcessor.UpdateMarkedItems(this);
|
||||
|
||||
@@ -193,9 +215,9 @@ namespace FileTime.Avalonia.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
_containers.Clear();
|
||||
_elements.Clear();
|
||||
_items.Clear();
|
||||
_containers = new ObservableCollection<ContainerViewModel>();
|
||||
_elements = new ObservableCollection<ElementViewModel>();
|
||||
_items = new ObservableCollection<IItemViewModel>();
|
||||
}
|
||||
|
||||
public async Task<ObservableCollection<ContainerViewModel>> GetContainers()
|
||||
|
||||
@@ -23,6 +23,9 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Timeline;
|
||||
using FileTime.Core.Providers;
|
||||
using Syroot.Windows.IO;
|
||||
using FileTime.Avalonia.IconProviders;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
@@ -42,6 +45,8 @@ namespace FileTime.Avalonia.ViewModels
|
||||
private IClipboard _clipboard;
|
||||
private TimeRunner _timeRunner;
|
||||
private IEnumerable<IContentProvider> _contentProviders;
|
||||
private IIconProvider _iconProvider;
|
||||
|
||||
private Func<Task>? _inputHandler;
|
||||
|
||||
[Property]
|
||||
@@ -59,9 +64,15 @@ namespace FileTime.Avalonia.ViewModels
|
||||
[Property]
|
||||
private List<RootDriveInfo> _rootDriveInfos;
|
||||
|
||||
[Property]
|
||||
private List<PlaceInfo> _places;
|
||||
|
||||
[Property]
|
||||
private string _messageBoxText;
|
||||
|
||||
[Property]
|
||||
private ObservableCollection<string> _popupTexts = new ObservableCollection<string>();
|
||||
|
||||
public IReadOnlyList<ReadOnlyParallelCommands> TimelineCommands => _timeRunner.ParallelCommands;
|
||||
|
||||
async partial void OnInitialize()
|
||||
@@ -69,8 +80,9 @@ namespace FileTime.Avalonia.ViewModels
|
||||
_clipboard = App.ServiceProvider.GetService<IClipboard>()!;
|
||||
_timeRunner = App.ServiceProvider.GetService<TimeRunner>()!;
|
||||
_contentProviders = App.ServiceProvider.GetService<IEnumerable<IContentProvider>>()!;
|
||||
_iconProvider = App.ServiceProvider.GetService<IIconProvider>()!;
|
||||
var inputInterface = (BasicInputHandler)App.ServiceProvider.GetService<IInputInterface>()!;
|
||||
inputInterface.InputHandler = ReadInputs2;
|
||||
inputInterface.InputHandler = ReadInputs;
|
||||
App.ServiceProvider.GetService<TopContainer>();
|
||||
|
||||
_timeRunner.CommandsChanged += (o, e) => OnPropertyChanged(nameof(TimelineCommands));
|
||||
@@ -103,8 +115,68 @@ namespace FileTime.Avalonia.ViewModels
|
||||
driveInfos.Add(driveInfo);
|
||||
}
|
||||
}
|
||||
|
||||
RootDriveInfos = driveInfos.OrderBy(d => d.Name).ToList();
|
||||
|
||||
var places = new List<PlaceInfo>();
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
var placesFolders = new List<KnownFolder>()
|
||||
{
|
||||
KnownFolders.Profile,
|
||||
KnownFolders.Desktop,
|
||||
KnownFolders.DocumentsLocalized,
|
||||
KnownFolders.DownloadsLocalized,
|
||||
KnownFolders.Music,
|
||||
KnownFolders.Pictures,
|
||||
KnownFolders.Videos,
|
||||
};
|
||||
|
||||
foreach (var placesFolder in placesFolders)
|
||||
{
|
||||
var possibleContainer = await LocalContentProvider.GetByPath(placesFolder.Path);
|
||||
if (possibleContainer is IContainer container)
|
||||
{
|
||||
var name = container.Name;
|
||||
if (await container.GetByPath("desktop.ini") is LocalFile element)
|
||||
{
|
||||
var lines = File.ReadAllLines(element.File.FullName);
|
||||
if (Array.Find(lines, l => l.StartsWith("localizedresourcename", StringComparison.OrdinalIgnoreCase)) is string nameLine)
|
||||
{
|
||||
var nameLineValue = string.Join('=', nameLine.Split('=')[1..]);
|
||||
var environemntVariables = Environment.GetEnvironmentVariables();
|
||||
foreach (var keyo in environemntVariables.Keys)
|
||||
{
|
||||
if (keyo is string key && environemntVariables[key] is string value)
|
||||
{
|
||||
nameLineValue = nameLineValue.Replace($"%{key}%", value);
|
||||
}
|
||||
}
|
||||
|
||||
if (nameLineValue.StartsWith("@"))
|
||||
{
|
||||
var parts = nameLineValue[1..].Split(',');
|
||||
if (parts.Length >= 2 && long.TryParse(parts[^1], out var parsedResourceId))
|
||||
{
|
||||
if (parsedResourceId < 0) parsedResourceId *= -1;
|
||||
|
||||
name = NativeMethodHelpers.GetStringResource(string.Join(',', parts[..^1]), (uint)parsedResourceId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
name = nameLineValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
places.Add(new PlaceInfo(name, container));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("TODO linux places");
|
||||
}
|
||||
Places = places;
|
||||
}
|
||||
|
||||
private async Task<IContainer?> GetContainerForWindowsDrive(DriveInfo drive)
|
||||
@@ -123,6 +195,12 @@ namespace FileTime.Avalonia.ViewModels
|
||||
await AppState.SelectedTab.Open();
|
||||
}
|
||||
|
||||
public async Task OpenContainer(IContainer container)
|
||||
{
|
||||
AppState.RapidTravelText = "";
|
||||
await AppState.SelectedTab.OpenContainer(container);
|
||||
}
|
||||
|
||||
public async Task OpenOrRun()
|
||||
{
|
||||
if (AppState.SelectedTab.SelectedItem is ContainerViewModel)
|
||||
@@ -530,6 +608,20 @@ namespace FileTime.Avalonia.ViewModels
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task ToggleAdvancedIcons()
|
||||
{
|
||||
_iconProvider.EnableAdvancedIcons = !_iconProvider.EnableAdvancedIcons;
|
||||
var text = "Advanced icons are: " + (_iconProvider.EnableAdvancedIcons ? "ON" : "OFF");
|
||||
_popupTexts.Add(text);
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
await Dispatcher.UIThread.InvokeAsync(() => _popupTexts.Remove(text));
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Command]
|
||||
public async void ProcessInputs()
|
||||
{
|
||||
@@ -714,7 +806,7 @@ namespace FileTime.Avalonia.ViewModels
|
||||
_inputHandler = inputHandler;
|
||||
}
|
||||
|
||||
public async Task<string?[]> ReadInputs2(IEnumerable<Core.Interactions.InputElement> fields)
|
||||
public async Task<string?[]> ReadInputs(IEnumerable<Core.Interactions.InputElement> fields)
|
||||
{
|
||||
var waiting = true;
|
||||
var result = new string[0];
|
||||
@@ -912,9 +1004,14 @@ namespace FileTime.Avalonia.ViewModels
|
||||
RefreshCurrentLocation),
|
||||
new CommandBinding(
|
||||
"go to",
|
||||
FileTime.App.Core.Command.Commands.Refresh,
|
||||
FileTime.App.Core.Command.Commands.Dummy,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.L, ctrl: true)},
|
||||
GoToContainer),
|
||||
new CommandBinding(
|
||||
"toggle advanced icons",
|
||||
FileTime.App.Core.Command.Commands.Dummy,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.Z),new KeyWithModifiers(Key.I)},
|
||||
ToggleAdvancedIcons),
|
||||
};
|
||||
var universalCommandBindings = new List<CommandBinding>()
|
||||
{
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
</ItemsControl>
|
||||
|
||||
<Grid Grid.Column="2" IsVisible="{Binding ShowAttributes,ElementName=ItemRoot}">
|
||||
<Grid ColumnDefinitions="180,40">
|
||||
<Grid ColumnDefinitions="180,45">
|
||||
<TextBlock Text="{Binding Item.CreatedAt}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding Item.Attributes}"/>
|
||||
</Grid>
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
@@ -36,7 +35,6 @@
|
||||
|
||||
<TextBlock
|
||||
Margin="0,0,0,10"
|
||||
Classes="NormalText"
|
||||
Text="Drives" />
|
||||
|
||||
<ItemsRepeater
|
||||
@@ -53,25 +51,35 @@
|
||||
VerticalAlignment="Center"
|
||||
Source="{SvgImage /Assets/material/folder.svg}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Stretch"
|
||||
Classes="NormalText"
|
||||
Text="{Binding FullName}" />
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding FullName}" />
|
||||
|
||||
<TextBlock
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Classes="SmallText"
|
||||
Text="{Binding Label}" IsVisible="{Binding Label,Converter={StaticResource IsNotEmptyConverter}}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel HorizontalAlignment="Right"
|
||||
Grid.Column="2"
|
||||
Orientation="Horizontal"
|
||||
VerticalAlignment="Center">
|
||||
|
||||
<TextBlock Classes="SmallText" Text="{Binding Free, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}">
|
||||
<TextBlock Classes="SmallText" VerticalAlignment="Center" Text="{Binding Free, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}">
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Classes="SmallText" Text=" / ">
|
||||
<TextBlock Classes="SmallText" VerticalAlignment="Center" Text=" / ">
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Classes="SmallText" Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}">
|
||||
<TextBlock Classes="SmallText" VerticalAlignment="Center" Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}">
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
@@ -79,8 +87,40 @@
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="2"
|
||||
Maximum="{Binding Size}"
|
||||
Value="{Binding Used}" />
|
||||
Maximum="100"
|
||||
Value="{Binding UsedPercentage}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="1" CornerRadius="10" Background="{DynamicResource ContainerBackgroundBrush}" Padding="0,10" Margin="10">
|
||||
<Grid RowDefinitions="Auto,Auto">
|
||||
|
||||
<TextBlock
|
||||
Margin="10,0,10,10"
|
||||
Text="Places" />
|
||||
|
||||
<ItemsRepeater
|
||||
Grid.Row="1"
|
||||
Items="{Binding Places}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Classes="PlacesItem" PointerPressed="OnPlacePointerPressed" Cursor="Hand">
|
||||
<StackPanel Orientation="Horizontal" Margin="10,5" HorizontalAlignment="Stretch">
|
||||
<Image
|
||||
Width="20"
|
||||
Height="20"
|
||||
VerticalAlignment="Center"
|
||||
Source="{Binding Container,Converter={StaticResource ItemToImageConverter}}" />
|
||||
|
||||
<TextBlock
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Name}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
@@ -234,15 +274,27 @@
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<TextBlock
|
||||
x:Name="ChildEmpty"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource ErrorBrush}"
|
||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
|
||||
Empty
|
||||
</TextBlock>
|
||||
<Grid
|
||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
|
||||
|
||||
<TextBlock
|
||||
x:Name="ChildEmpty"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource ErrorBrush}"
|
||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Exceptions, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
||||
Empty
|
||||
</TextBlock>
|
||||
|
||||
<ItemsRepeater Items="{Binding AppState.SelectedTab.ChildContainer.Exceptions}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -314,6 +366,28 @@
|
||||
Content="No" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<ItemsRepeater Items="{Binding PopupTexts}" Margin="0,0,0,20" HorizontalAlignment="Center" VerticalAlignment="Bottom" IsVisible="{Binding PopupTexts.Count,Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
||||
<ItemsRepeater.Styles>
|
||||
<Style Selector="TextBlock">
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:1">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="Opacity" Value="0.0"/>
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="Opacity" Value="1.0"/>
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
</ItemsRepeater.Styles>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}" HorizontalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="3">
|
||||
|
||||
@@ -4,6 +4,7 @@ using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using FileTime.Avalonia.Misc;
|
||||
using FileTime.Avalonia.Models;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using System.Linq;
|
||||
|
||||
@@ -86,12 +87,25 @@ namespace FileTime.Avalonia.Views
|
||||
}
|
||||
}
|
||||
|
||||
private void InputText_AttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs args)
|
||||
private void InputText_AttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (sender is TextBox inputText && inputText.DataContext is InputElementWrapper inputElementWrapper && inputElementWrapper == ViewModel!.Inputs.First())
|
||||
{
|
||||
inputText.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlacePointerPressed(object sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if(!e.Handled
|
||||
&& ViewModel != null
|
||||
&& e.GetCurrentPoint(this).Properties.IsLeftButtonPressed
|
||||
&& sender is StyledElement control
|
||||
&& control.DataContext is PlaceInfo placeInfo)
|
||||
{
|
||||
ViewModel.OpenContainer(placeInfo.Container).Wait();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user