IconProvider, container exceptions, refactor

This commit is contained in:
2022-02-02 11:02:37 +01:00
parent c4bf417ba3
commit 3e553dd448
31 changed files with 597 additions and 132 deletions

View File

@@ -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>

View 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();
}
}
}

View File

@@ -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 };
}

View File

@@ -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" />

View File

@@ -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; }
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View 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);
}*/
}
}

View 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;
}
}
}

View File

@@ -0,0 +1,9 @@
namespace FileTime.Avalonia.Models
{
public enum ImagePathType
{
Asset,
Absolute,
Raw
}
}

View 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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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))
{

View File

@@ -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()

View File

@@ -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>()
{

View File

@@ -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>

View File

@@ -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">

View File

@@ -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;
}
}
}
}