MainWindow skeleton

This commit is contained in:
2022-04-03 09:22:24 +02:00
parent 7ff3898bd9
commit b6b8a7b3f8
28 changed files with 432 additions and 105 deletions

View File

@@ -7,6 +7,7 @@ namespace FileTime.App.Core
{ {
ObservableCollection<ITabViewModel> Tabs { get; } ObservableCollection<ITabViewModel> Tabs { get; }
ITabViewModel? SelectedTab { get; } ITabViewModel? SelectedTab { get; }
IObservable<ITabViewModel?> SelectedTabObservable { get; }
IObservable<string?> SearchText { get; } IObservable<string?> SearchText { get; }
void AddTab(ITabViewModel tabViewModel); void AddTab(ITabViewModel tabViewModel);

View File

@@ -1,6 +1,9 @@
using FileTime.Core.Models;
using InitableService;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels
{ {
public interface IContainerSizeContainerViewModel : IItemViewModel public interface IContainerSizeContainerViewModel : IItemViewModel, IInitable<IContainer, ITabViewModel, int>
{ {
long Size { get; set; } long Size { get; set; }
} }

View File

@@ -1,6 +1,9 @@
using FileTime.Core.Models;
using InitableService;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels
{ {
public interface IContainerViewModel : IItemViewModel public interface IContainerViewModel : IItemViewModel, IInitable<IContainer, ITabViewModel, int>
{ {
} }
} }

View File

@@ -1,6 +1,9 @@
using FileTime.Core.Models;
using InitableService;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels
{ {
public interface IElementViewModel : IItemViewModel public interface IElementViewModel : IItemViewModel, IInitable<IElement, ITabViewModel, int>
{ {
long? Size { get; set; } long? Size { get; set; }
} }

View File

@@ -1,6 +1,9 @@
using FileTime.Core.Models;
using InitableService;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels
{ {
public interface IFileViewModel : IElementViewModel public interface IFileViewModel : IElementViewModel, IInitable<IFileElement, ITabViewModel, int>
{ {
} }
} }

View File

@@ -2,13 +2,15 @@ using System.Reactive.Subjects;
using FileTime.App.Core.Models; using FileTime.App.Core.Models;
using FileTime.App.Core.Models.Enums; using FileTime.App.Core.Models.Enums;
using FileTime.Core.Models; using FileTime.Core.Models;
using InitableService;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels
{ {
public interface IItemViewModel public interface IItemViewModel : IInitable<IItem, ITabViewModel, int>
{ {
IItem? BaseItem { get; set; } IItem? BaseItem { get; set; }
IObservable<IReadOnlyList<ItemNamePart>>? DisplayName { get; set; } IObservable<IReadOnlyList<ItemNamePart>>? DisplayName { get; set; }
string? DisplayNameText { get; set; }
IObservable<bool>? IsSelected { get; set; } IObservable<bool>? IsSelected { get; set; }
IObservable<bool>? IsMarked { get; set; } IObservable<bool>? IsMarked { get; set; }
BehaviorSubject<bool> IsAlternative { get; } BehaviorSubject<bool> IsAlternative { get; }

View File

@@ -5,12 +5,14 @@ using InitableService;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels
{ {
public interface ITabViewModel : IInitable<ITab> public interface ITabViewModel : IInitable<ITab, int>
{ {
ITab? Tab { get; }
int TabNumber { get; }
IObservable<bool> IsSelected { get; }
IObservable<IContainer?>? CurrentLocation { get; } IObservable<IContainer?>? CurrentLocation { get; }
IObservable<IItemViewModel?>? CurrentSelectedItem { get; } IObservable<IItemViewModel?>? CurrentSelectedItem { get; }
IObservable<IReadOnlyList<IItemViewModel>>? CurrentItems { get; } IObservable<IReadOnlyList<IItemViewModel>>? CurrentItems { get; }
IObservable<IReadOnlyList<FullName>> MarkedItems { get; } IObservable<IReadOnlyList<FullName>> MarkedItems { get; }
ITab? Tab { get; }
} }
} }

View File

@@ -10,15 +10,31 @@ namespace FileTime.App.Core
public abstract partial class AppStateBase : IAppState public abstract partial class AppStateBase : IAppState
{ {
private readonly BehaviorSubject<string?> _searchText = new(null); private readonly BehaviorSubject<string?> _searchText = new(null);
private readonly BehaviorSubject<ITabViewModel?> _selectedTabObservable = new(null);
private ITabViewModel? _selectedTab;
public ObservableCollection<ITabViewModel> Tabs { get; } = new(); public ObservableCollection<ITabViewModel> Tabs { get; } = new();
public IObservable<string?> SearchText { get; private set; } public IObservable<string?> SearchText { get; private set; }
[Property] public IObservable<ITabViewModel?> SelectedTabObservable { get; private set; }
private ITabViewModel? _selectedTab; public ITabViewModel? SelectedTab
{
get => _selectedTab;
private set
{
if (value != _selectedTab)
{
_selectedTab = value;
OnPropertyChanged(nameof(SelectedTab));
_selectedTabObservable.OnNext(value);
}
}
}
partial void OnInitialize() partial void OnInitialize()
{ {
SearchText = _searchText.AsObservable(); SearchText = _searchText.AsObservable();
SelectedTabObservable = _selectedTabObservable.AsObservable();
} }
public void AddTab(ITabViewModel tabViewModel) public void AddTab(ITabViewModel tabViewModel)
@@ -45,5 +61,10 @@ namespace FileTime.App.Core
{ {
_searchText.OnNext(searchText); _searchText.OnNext(searchText);
} }
public void SetSelectedTab(ITabViewModel tabToSelect)
{
SelectedTab = tabToSelect;
}
} }
} }

View File

@@ -1,11 +1,22 @@
using FileTime.App.Core.Services;
using FileTime.Core.Models;
using MvvmGen; using MvvmGen;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels
{ {
[ViewModel] [ViewModel(GenerateConstructor = false)]
public partial class ContainerSizeContainerViewModel : ItemViewModel, IContainerSizeContainerViewModel public partial class ContainerSizeContainerViewModel : ItemViewModel, IContainerSizeContainerViewModel
{ {
[Property] [Property]
private long _size; private long _size;
public ContainerSizeContainerViewModel(IItemNameConverterService _itemNameConverterService, IAppState _appState) : base(_itemNameConverterService, _appState)
{
}
public void Init(IContainer item, ITabViewModel parentTab, int index)
{
Init((IItem)item, parentTab, index);
}
} }
} }

View File

@@ -1,6 +1,19 @@
using FileTime.App.Core.Services;
using FileTime.Core.Models;
using MvvmGen;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels
{ {
public class ContainerViewModel : ItemViewModel, IContainerViewModel [ViewModel(GenerateConstructor = false)]
public partial class ContainerViewModel : ItemViewModel, IContainerViewModel
{
public ContainerViewModel(IItemNameConverterService _itemNameConverterService, IAppState _appState) : base(_itemNameConverterService, _appState)
{ {
} }
public void Init(IContainer item, ITabViewModel parentTab, int index)
{
Init((IItem)item, parentTab, index);
}
}
} }

View File

@@ -1,11 +1,22 @@
using FileTime.App.Core.Services;
using FileTime.Core.Models;
using MvvmGen; using MvvmGen;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels
{ {
[ViewModel] [ViewModel(GenerateConstructor = false)]
public partial class ElementViewModel : ItemViewModel, IElementViewModel public partial class ElementViewModel : ItemViewModel, IElementViewModel
{ {
[Property] [Property]
private long? _size; private long? _size;
public ElementViewModel(IItemNameConverterService _itemNameConverterService, IAppState _appState) : base(_itemNameConverterService, _appState)
{
}
public void Init(IElement item, ITabViewModel parentTab, int index)
{
Init((IItem)item, parentTab, index);
}
} }
} }

View File

@@ -1,6 +1,19 @@
using FileTime.App.Core.Services;
using FileTime.Core.Models;
using MvvmGen;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels
{ {
public class FileViewModel : ElementViewModel, IFileViewModel [ViewModel(GenerateConstructor = false)]
public partial class FileViewModel : ElementViewModel, IFileViewModel
{
public FileViewModel(IItemNameConverterService _itemNameConverterService, IAppState _appState) : base(_itemNameConverterService, _appState)
{ {
} }
public void Init(IFileElement item, ITabViewModel parentTab, int index)
{
Init((IElement)item, parentTab, index);
}
}
} }

View File

@@ -1,12 +1,16 @@
using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using FileTime.App.Core.Models; using FileTime.App.Core.Models;
using FileTime.App.Core.Models.Enums; using FileTime.App.Core.Models.Enums;
using FileTime.App.Core.Services;
using FileTime.Core.Models; using FileTime.Core.Models;
using MvvmGen; using MvvmGen;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels
{ {
[ViewModel] [ViewModel]
[Inject(typeof(IAppState), "_appState")]
[Inject(typeof(IItemNameConverterService), "_itemNameConverterService")]
public abstract partial class ItemViewModel : IItemViewModel public abstract partial class ItemViewModel : IItemViewModel
{ {
[Property] [Property]
@@ -15,6 +19,9 @@ namespace FileTime.App.Core.ViewModels
[Property] [Property]
private IObservable<IReadOnlyList<ItemNamePart>>? _displayName; private IObservable<IReadOnlyList<ItemNamePart>>? _displayName;
[Property]
private string? _displayNameText;
[Property] [Property]
private IObservable<bool>? _isSelected; private IObservable<bool>? _isSelected;
@@ -32,5 +39,29 @@ namespace FileTime.App.Core.ViewModels
[Property] [Property]
private BehaviorSubject<bool> _isAlternative = new(false); private BehaviorSubject<bool> _isAlternative = new(false);
public void Init(IItem item, ITabViewModel parentTab, int index)
{
BaseItem = item;
DisplayName = _appState.SearchText.Select(s => _itemNameConverterService.GetDisplayName(item.DisplayName, s));
DisplayNameText = item.DisplayName;
IsMarked = parentTab.MarkedItems.Select(m => m.Contains(item.FullName));
IsSelected = parentTab.MarkedItems.Select(m => m.Contains(item.FullName));
IsAlternative.OnNext(index % 2 == 0);
ViewMode = Observable.CombineLatest(IsMarked, IsSelected, IsAlternative, GenerateViewMode);
Attributes = item.Attributes;
CreatedAt = item.CreatedAt;
}
private ItemViewMode GenerateViewMode(bool isMarked, bool isSelected, bool sAlternative)
=> (isMarked, isSelected, sAlternative) switch
{
(true, true, _) => ItemViewMode.MarkedSelected,
(true, false, true) => ItemViewMode.MarkedAlternative,
(false, true, _) => ItemViewMode.Selected,
(false, false, true) => ItemViewMode.Alternative,
(true, false, false) => ItemViewMode.Marked,
_ => ItemViewMode.Default
};
} }
} }

View File

@@ -17,13 +17,16 @@ namespace FileTime.App.Core.ViewModels
private readonly List<IDisposable> _disposables = new(); private readonly List<IDisposable> _disposables = new();
private bool disposed; private bool disposed;
public ITab? Tab { get; private set; }
public int TabNumber { get; private set; }
public IObservable<bool> IsSelected { get; }
public IObservable<IContainer?>? CurrentLocation { get; private set; } public IObservable<IContainer?>? CurrentLocation { get; private set; }
public IObservable<IItemViewModel?>? CurrentSelectedItem { get; private set; } public IObservable<IItemViewModel?>? CurrentSelectedItem { get; private set; }
public IObservable<IReadOnlyList<IItemViewModel>>? CurrentItems { get; private set; } public IObservable<IReadOnlyList<IItemViewModel>>? CurrentItems { get; private set; }
public IObservable<IReadOnlyList<FullName>> MarkedItems { get; } public IObservable<IReadOnlyList<FullName>> MarkedItems { get; }
public ITab? Tab { get; private set; }
public TabViewModel( public TabViewModel(
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
IItemNameConverterService itemNameConverterService, IItemNameConverterService itemNameConverterService,
@@ -34,10 +37,14 @@ namespace FileTime.App.Core.ViewModels
_appState = appState; _appState = appState;
MarkedItems = _markedItems.Select(e => e.ToList()).AsObservable(); MarkedItems = _markedItems.Select(e => e.ToList()).AsObservable();
IsSelected = _appState.SelectedTabObservable.Select(s => s == this);
} }
public void Init(ITab tab) public void Init(ITab tab, int tabNumber)
{ {
Tab = tab;
TabNumber = tabNumber;
CurrentLocation = tab.CurrentLocation.AsObservable(); CurrentLocation = tab.CurrentLocation.AsObservable();
CurrentItems = tab.CurrentItems.Select(items => items.Select(MapItemToViewModel).ToList()); CurrentItems = tab.CurrentItems.Select(items => items.Select(MapItemToViewModel).ToList());
CurrentSelectedItem = Observable.CombineLatest( CurrentSelectedItem = Observable.CombineLatest(
@@ -45,60 +52,32 @@ namespace FileTime.App.Core.ViewModels
tab.CurrentSelectedItem, tab.CurrentSelectedItem,
(currentItems, currentSelectedItemPath) => currentItems.FirstOrDefault(i => i.BaseItem?.FullName == currentSelectedItemPath?.Path)); (currentItems, currentSelectedItemPath) => currentItems.FirstOrDefault(i => i.BaseItem?.FullName == currentSelectedItemPath?.Path));
tab.CurrentLocation.Subscribe((_) => _markedItems.OnNext(Enumerable.Empty<FullName>())); tab.CurrentLocation.Subscribe((_) => _markedItems.OnNext(Enumerable.Empty<FullName>()));
Tab = tab;
} }
private IItemViewModel MapItemToViewModel(IItem item, int index) private IItemViewModel MapItemToViewModel(IItem item, int index)
{ {
if (item is IContainer container) if (item is IContainer container)
{ {
var containerViewModel = _serviceProvider.GetRequiredService<IContainerViewModel>(); var containerViewModel = _serviceProvider.GetInitableResolver<IContainer, ITabViewModel, int>(container, this, index).GetRequiredService<IContainerViewModel>();
InitIItemViewModel(containerViewModel, item);
return containerViewModel; return containerViewModel;
} }
else if (item is IFileElement fileElement) else if (item is IFileElement fileElement)
{ {
var fileViewModel = _serviceProvider.GetRequiredService<IFileViewModel>(); var fileViewModel = _serviceProvider.GetInitableResolver<IFileElement, ITabViewModel, int>(fileElement, this, index).GetRequiredService<IFileViewModel>();
InitIItemViewModel(fileViewModel, item);
fileViewModel.Size = fileElement.Size; fileViewModel.Size = fileElement.Size;
return fileViewModel; return fileViewModel;
} }
else if (item is IElement element) else if (item is IElement element)
{ {
var elementViewModel = _serviceProvider.GetRequiredService<IElementViewModel>(); var elementViewModel = _serviceProvider.GetInitableResolver<IElement, ITabViewModel, int>(element, this, index).GetRequiredService<IElementViewModel>();
InitIItemViewModel(elementViewModel, item);
return elementViewModel; return elementViewModel;
} }
throw new ArgumentException($"{nameof(item)} is not {nameof(IContainer)} neighter {nameof(IElement)}"); throw new ArgumentException($"{nameof(item)} is not {nameof(IContainer)} neighter {nameof(IElement)}");
void InitIItemViewModel(IItemViewModel itemViewModel, IItem item)
{
itemViewModel.BaseItem = item;
itemViewModel.DisplayName = _appState.SearchText.Select(s => _itemNameConverterService.GetDisplayName(item.DisplayName, s));
itemViewModel.IsMarked = MarkedItems.Select(m => m.Contains(item.FullName));
itemViewModel.IsSelected = MarkedItems.Select(m => m.Contains(item.FullName));
itemViewModel.IsAlternative.OnNext(index % 2 == 0);
itemViewModel.ViewMode = Observable.CombineLatest(itemViewModel.IsMarked, itemViewModel.IsSelected, itemViewModel.IsAlternative, GenerateViewMode);
itemViewModel.Attributes = item.Attributes;
itemViewModel.CreatedAt = item.CreatedAt;
} }
}
private ItemViewMode GenerateViewMode(bool isMarked, bool isSelected, bool sAlternative)
=> (isMarked, isSelected, sAlternative) switch
{
(true, true, _) => ItemViewMode.MarkedSelected,
(true, false, true) => ItemViewMode.MarkedAlternative,
(false, true, _) => ItemViewMode.Selected,
(false, false, true) => ItemViewMode.Alternative,
(true, false, false) => ItemViewMode.Marked,
_ => ItemViewMode.Default
};
~TabViewModel() => Dispose(false); ~TabViewModel() => Dispose(false);

View File

@@ -2,6 +2,7 @@ namespace FileTime.Core.Models
{ {
public interface IContainer : IItem public interface IContainer : IItem
{ {
IReadOnlyList<IAbsolutePath> Items { get; } IObservable<IReadOnlyList<IAbsolutePath>> Items { get; }
IObservable<bool> IsLoading { get; }
} }
} }

View File

@@ -1,3 +1,5 @@
using System.Reactive.Linq;
using System.Reactive.Subjects;
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Services; using FileTime.Core.Services;
@@ -16,5 +18,9 @@ namespace FileTime.Core.Models
bool CanRename, bool CanRename,
string? Attributes, string? Attributes,
IContentProvider Provider, IContentProvider Provider,
IReadOnlyList<IAbsolutePath> Items) : IContainer; IObservable<IReadOnlyList<IAbsolutePath>> Items) : IContainer
{
BehaviorSubject<bool> IsLoading { get; } = new BehaviorSubject<bool>(false);
IObservable<bool> IContainer.IsLoading => IsLoading.AsObservable();
}
} }

View File

@@ -1,3 +1,5 @@
using System.Reactive.Linq;
using System.Reactive.Subjects;
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Models; using FileTime.Core.Models;
@@ -5,9 +7,9 @@ namespace FileTime.Core.Services
{ {
public abstract class ContentProviderBase : IContentProvider public abstract class ContentProviderBase : IContentProvider
{ {
protected List<IAbsolutePath> Items { get; set; } = new List<IAbsolutePath>(); protected BehaviorSubject<IReadOnlyList<IAbsolutePath>> Items { get; } = new BehaviorSubject<IReadOnlyList<IAbsolutePath>>(new List<IAbsolutePath>());
IReadOnlyList<IAbsolutePath> IContainer.Items => Items; IObservable<IReadOnlyList<IAbsolutePath>> IContainer.Items => Items;
public string Name { get; } public string Name { get; }
@@ -33,6 +35,10 @@ namespace FileTime.Core.Services
public string? Attributes => null; public string? Attributes => null;
protected BehaviorSubject<bool> IsLoading { get; } = new(false);
IObservable<bool> IContainer.IsLoading => IsLoading.AsObservable();
protected ContentProviderBase(string name) protected ContentProviderBase(string name)
{ {
DisplayName = Name = name; DisplayName = Name = name;

View File

@@ -15,12 +15,15 @@ namespace FileTime.Core.Services
public Tab() public Tab()
{ {
CurrentLocation = _currentLocation.AsObservable(); CurrentLocation = _currentLocation.AsObservable();
CurrentItems = _currentLocation CurrentItems =
.Select(c => Observable.Merge(
Observable.FromAsync(async () => _currentLocation
c == null .Where(c => c is not null)
? Enumerable.Empty<IItem>() .Select(c => c!.Items)
: await c.Items .Switch()
.Select(
i => Observable.FromAsync(async () =>
await i
.ToAsyncEnumerable() .ToAsyncEnumerable()
.SelectAwait( .SelectAwait(
async i => async i =>
@@ -32,14 +35,18 @@ namespace FileTime.Core.Services
} }
catch { return null!; } catch { return null!; }
} }
) )
.Where(i => i != null) .Where(i => i != null)
.ToListAsync() .ToListAsync()
) )
) )
.Merge(Constants.MaximumObservableMergeOperations); .Merge(Constants.MaximumObservableMergeOperations),
CurrentSelectedItem = CurrentLocation.Select(GetSelectedItemByLocation).Merge(_currentSelectedItem).Throttle(TimeSpan.FromMilliseconds(500)); _currentLocation
.Where(c => c is null)
.Select(c => Enumerable.Empty<IItem>())
);
CurrentSelectedItem = CurrentLocation.Select(GetSelectedItemByLocation).Switch().Merge(_currentSelectedItem).Throttle(TimeSpan.FromMilliseconds(500));
} }
public void Init(IContainer currentLocation) public void Init(IContainer currentLocation)
@@ -47,9 +54,9 @@ namespace FileTime.Core.Services
_currentLocation.OnNext(currentLocation); _currentLocation.OnNext(currentLocation);
} }
private IAbsolutePath? GetSelectedItemByLocation(IContainer? currentLocation) private IObservable<IAbsolutePath?> GetSelectedItemByLocation(IContainer? currentLocation)
{ {
return currentLocation?.Items[0]; return currentLocation?.Items?.Select(i => i.FirstOrDefault()) ?? Observable.Never((IAbsolutePath?)null);
} }
public void ChangeLocation(IContainer newLocation) public void ChangeLocation(IContainer newLocation)

View File

@@ -132,6 +132,9 @@
<converters:GetFileExtensionConverter x:Key="GetFileExtensionConverter"/> <converters:GetFileExtensionConverter x:Key="GetFileExtensionConverter"/>
<converters:FormatSizeConverter x:Key="FormatSizeConverter"/> <converters:FormatSizeConverter x:Key="FormatSizeConverter"/>
<converters:DateTimeConverter x:Key="DateTimeConverter"/> <converters:DateTimeConverter x:Key="DateTimeConverter"/>
<converters:SplitStringConverter x:Key="SplitStringConverter" />
<converters:CompareConverter x:Key="EqualityConverter"/>
<converters:CompareConverter x:Key="NotEqualsConverter" ComparisonCondition="{x:Static converters:ComparisonCondition.NotEqual}"/>
</ResourceDictionary> </ResourceDictionary>
</Application.Resources> </Application.Resources>

View File

@@ -7,6 +7,7 @@
<TrimMode>copyused</TrimMode> <TrimMode>copyused</TrimMode>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport> <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationIcon>filetime.ico</ApplicationIcon> <ApplicationIcon>filetime.ico</ApplicationIcon>
<Version>0.0.1</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Content Include="filetime.ico" /> <Content Include="filetime.ico" />

View File

@@ -0,0 +1,3 @@
<svg style="width:24px;height:24px" viewBox="0 0 24 24">
<path fill="white" d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" />
</svg>

After

Width:  |  Height:  |  Size: 136 B

View File

@@ -0,0 +1,54 @@
using System.Globalization;
using Avalonia.Data.Converters;
namespace FileTime.GuiApp.Converters
{
public enum ComparisonCondition
{
Equal,
GreaterThan,
LessThan,
LessThanOrEqual,
NotEqual,
GreaterThanOrEqual
}
public class CompareConverter : IValueConverter
{
public ComparisonCondition ComparisonCondition { get; set; } = ComparisonCondition.Equal;
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return Compare(value, parameter);
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
private bool Compare(object? value, object? parameter)
{
if (ComparisonCondition == ComparisonCondition.GreaterThan)
{
if (value is int valueInt && (parameter is int parameterInt || int.TryParse(parameter?.ToString(), out parameterInt))) return valueInt > parameterInt;
else if (value is double valueDouble && (parameter is double parameterDouble || double.TryParse(parameter?.ToString(), out parameterDouble))) return valueDouble > parameterDouble;
else throw new NotSupportedException();
}
else if (ComparisonCondition == ComparisonCondition.NotEqual)
{
if (value is int valueInt && (parameter is int parameterInt || int.TryParse(parameter?.ToString(), out parameterInt))) return valueInt != parameterInt;
else if (value is double valueDouble && (parameter is double parameterDouble || double.TryParse(parameter?.ToString(), out parameterDouble))) return valueDouble != parameterDouble;
return value != parameter;
}
else if (ComparisonCondition == ComparisonCondition.Equal)
{
if (value is int valueInt && (parameter is int parameterInt || int.TryParse(parameter?.ToString(), out parameterInt))) return valueInt == parameterInt;
else if (value is double valueDouble && (parameter is double parameterDouble || double.TryParse(parameter?.ToString(), out parameterDouble))) return valueDouble == parameterDouble;
else if (value?.GetType().IsEnum ?? false && Enum.TryParse(value.GetType(), parameter?.ToString(), out var _)) return value.ToString() == parameter?.ToString();
}
return value == parameter;
}
}
}

View File

@@ -0,0 +1,27 @@
using System.Globalization;
using Avalonia.Data.Converters;
namespace FileTime.GuiApp.Converters
{
public class SplitStringConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is string path && parameter is string separator)
{
return path.Split(separator);
}
else if (value is string path2 && parameter is char separator2)
{
return path2.Split(separator2);
}
return value;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -13,16 +13,6 @@
<ItemGroup> <ItemGroup>
<AvaloniaResource Include="Assets\**" /> <AvaloniaResource Include="Assets\**" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<AvaloniaXaml Update="Views\MainWindow.axaml">
<Generator>MSBuild:Compile</Generator>
</AvaloniaXaml>
</ItemGroup>
<ItemGroup>
<Compile Update="Views\MainWindow.axaml.cs">
<DependentUpon>MainWindow.axaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.13" /> <PackageReference Include="Avalonia" Version="0.10.13" />
@@ -30,6 +20,7 @@
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--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.13" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.13" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.13" /> <PackageReference Include="Avalonia.ReactiveUI" Version="0.10.13" />
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.12" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="MvvmGen" Version="1.1.5" /> <PackageReference Include="MvvmGen" Version="1.1.5" />

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Reflection;
using Avalonia.Input; using Avalonia.Input;
using FileTime.App.Core; using FileTime.App.Core;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
@@ -6,6 +7,7 @@ using FileTime.Core.Models;
using FileTime.Core.Services; using FileTime.Core.Services;
using FileTime.Providers.Local; using FileTime.Providers.Local;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MvvmGen; using MvvmGen;
namespace FileTime.GuiApp.ViewModels namespace FileTime.GuiApp.ViewModels
@@ -14,17 +16,34 @@ namespace FileTime.GuiApp.ViewModels
[Inject(typeof(IAppState), "_appState")] [Inject(typeof(IAppState), "_appState")]
[Inject(typeof(ILocalContentProvider), "_localContentProvider")] [Inject(typeof(ILocalContentProvider), "_localContentProvider")]
[Inject(typeof(IServiceProvider), PropertyName = "_serviceProvider")] [Inject(typeof(IServiceProvider), PropertyName = "_serviceProvider")]
[Inject(typeof(ILogger<MainWindowViewModel>), PropertyName = "_logger")]
public partial class MainWindowViewModel : IMainWindowViewModelBase public partial class MainWindowViewModel : IMainWindowViewModelBase
{ {
public bool Loading => false; public bool Loading => false;
public IAppState AppState => _appState; public IAppState AppState => _appState;
public string Title { get; private set; }
partial void OnInitialize() partial void OnInitialize()
{ {
_logger?.LogInformation($"Starting {nameof(MainWindowViewModel)} initialization...");
var version = Assembly.GetEntryAssembly()!.GetName().Version;
var versionString = "Unknwon version";
if (version != null)
{
versionString = $"{version.Major}.{version.Minor}.{version.Build}";
if (version.Revision != 0)
{
versionString += $" ({version.Revision})";
}
}
Title = "FileTime " + versionString;
//TODO: refactor
if (AppState.Tabs.Count == 0) if (AppState.Tabs.Count == 0)
{ {
var tab = _serviceProvider.GetInitableResolver<IContainer>(_localContentProvider).GetRequiredService<ITab>(); var tab = _serviceProvider.GetInitableResolver<IContainer>(_localContentProvider).GetRequiredService<ITab>();
var tabViewModel = _serviceProvider.GetInitableResolver(tab).GetRequiredService<ITabViewModel>(); var tabViewModel = _serviceProvider.GetInitableResolver(tab, 1).GetRequiredService<ITabViewModel>();
_appState.AddTab(tabViewModel); _appState.AddTab(tabViewModel);
} }

View File

@@ -23,6 +23,75 @@
<Grid Background="{DynamicResource AppBackgroundBrush}"> <Grid Background="{DynamicResource AppBackgroundBrush}">
<Grid IsVisible="{Binding Loading, Converter={x:Static BoolConverters.Not}}"> <Grid IsVisible="{Binding Loading, Converter={x:Static BoolConverters.Not}}">
<Grid ColumnDefinitions="250,*" RowDefinitions="Auto,*"> <Grid ColumnDefinitions="250,*" RowDefinitions="Auto,*">
<Grid PointerPressed="HeaderPointerPressed">
<Rectangle Fill="#01000000"/>
<TextBlock Margin="15,10" Text="{Binding Title}"/>
</Grid>
<Grid Grid.Column="1" PointerPressed="HeaderPointerPressed">
<Rectangle Fill="#01000000"/>
<StackPanel Margin="20,10" Orientation="Horizontal">
<!--local:PathPresenter DataContext="{Binding AppState.SelectedTab.CurrentLocation^.FullName.Path,Converter={StaticResource PathPreformatter}}"/-->
<TextBlock
Text="{Binding AppState.SelectedTab.CurrentSelectedItem^.DisplayNameText}" Foreground="{StaticResource AccentBrush}" />
</StackPanel>
</Grid>
<Grid Grid.Column="1" Grid.Row="1" RowDefinitions="Auto,40,*,Auto">
<ItemsControl
Grid.Row="1"
Items="{Binding AppState.Tabs}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid RowDefinitions="Auto,1">
<StackPanel Orientation="Horizontal" Margin="20,0,20,0">
<TextBlock
VerticalAlignment="Center" Text="{Binding TabNumber,StringFormat=({0})}" />
<TextBlock
VerticalAlignment="Center" Margin="5,0,0,0" Text="{Binding CurrentLocation^.Name}" />
</StackPanel>
<Rectangle Fill="{DynamicResource ForegroundBrush}" Grid.Row="1" IsVisible="{Binding IsSelected^}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Grid
Grid.Row="2"
Margin="20,0,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="15*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="40*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="45*" />
</Grid.ColumnDefinitions>
<Rectangle
Grid.Column="1"
Width="1"
Margin="0,10,0,10"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Fill="{DynamicResource ContentSeparatorBrush}" />
<Grid Grid.Column="2" RowDefinitions="Auto,*">
<Grid IsVisible="{Binding AppState.SelectedTab.CurrentLocation^.IsLoading^}">
<Image Width="40" Height="40" Source="{SvgImage /Assets/loading.svg}" Classes="LoadingAnimation"/>
</Grid>
<ListBox <ListBox
Grid.Row="1" Grid.Row="1"
x:Name="CurrentItems" x:Name="CurrentItems"
@@ -39,6 +108,31 @@
</DataTemplate> </DataTemplate>
</ListBox.ItemTemplate> </ListBox.ItemTemplate>
</ListBox> </ListBox>
<TextBlock
Grid.Row="1"
x:CompileBindings="False"
x:Name="CurrentEmpty"
Margin="10"
HorizontalAlignment="Center"
FontWeight="Bold"
Foreground="{DynamicResource ErrorBrush}"
IsVisible="{Binding AppState.SelectedTab.CurrentLocation^.Items^.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
Empty
</TextBlock>
</Grid>
<Rectangle
Grid.Column="3"
Width="1"
Margin="0,10,0,10"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Fill="{DynamicResource ContentSeparatorBrush}" />
</Grid>
</Grid>
</Grid>
</Grid> </Grid>
<!--Borders--> <!--Borders-->

View File

@@ -49,5 +49,24 @@ namespace FileTime.GuiApp.Views
ViewModel?.ProcessKeyDown(e.Key, e.KeyModifiers, h => e.Handled = h); ViewModel?.ProcessKeyDown(e.Key, e.KeyModifiers, h => e.Handled = h);
} }
} }
private void HeaderPointerPressed(object sender, PointerPressedEventArgs e)
{
if (e.ClickCount == 2)
{
if (WindowState == WindowState.Maximized)
{
WindowState = WindowState.Normal;
}
else
{
WindowState = WindowState.Maximized;
}
}
else
{
BeginMoveDrag(e);
}
}
} }
} }

View File

@@ -1,3 +1,4 @@
using System.Reactive.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Models; using FileTime.Core.Models;
@@ -27,8 +28,7 @@ namespace FileTime.Providers.Local
? new DirectoryInfo("/").GetDirectories() ? new DirectoryInfo("/").GetDirectories()
: Environment.GetLogicalDrives().Select(d => new DirectoryInfo(d)); : Environment.GetLogicalDrives().Select(d => new DirectoryInfo(d));
Items.Clear(); Items.OnNext(rootDirectories.Select(DirectoryToAbsolutePath).ToList());
Items.AddRange(rootDirectories.Select(DirectoryToAbsolutePath));
} }
public override Task<IItem> GetItemByNativePathAsync(NativePath nativePath) public override Task<IItem> GetItemByNativePathAsync(NativePath nativePath)
@@ -78,7 +78,7 @@ namespace FileTime.Providers.Local
true, true,
GetDirectoryAttributes(directoryInfo), GetDirectoryAttributes(directoryInfo),
this, this,
GetItemsByContainer(directoryInfo) Observable.Return(GetItemsByContainer(directoryInfo))
); );
} }