diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/IAppState.cs b/src/AppCommon/FileTime.App.Core.Abstraction/IAppState.cs index 60333d9..b764327 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/IAppState.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/IAppState.cs @@ -7,6 +7,7 @@ namespace FileTime.App.Core { ObservableCollection Tabs { get; } ITabViewModel? SelectedTab { get; } + IObservable SelectedTabObservable { get; } IObservable SearchText { get; } void AddTab(ITabViewModel tabViewModel); diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IContainerSizeContainerViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IContainerSizeContainerViewModel.cs index 855ef19..0be724c 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IContainerSizeContainerViewModel.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IContainerSizeContainerViewModel.cs @@ -1,6 +1,9 @@ +using FileTime.Core.Models; +using InitableService; + namespace FileTime.App.Core.ViewModels { - public interface IContainerSizeContainerViewModel : IItemViewModel + public interface IContainerSizeContainerViewModel : IItemViewModel, IInitable { long Size { get; set; } } diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IContainerViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IContainerViewModel.cs index b94577e..0f482c5 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IContainerViewModel.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IContainerViewModel.cs @@ -1,6 +1,9 @@ +using FileTime.Core.Models; +using InitableService; + namespace FileTime.App.Core.ViewModels { - public interface IContainerViewModel : IItemViewModel + public interface IContainerViewModel : IItemViewModel, IInitable { } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IElementViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IElementViewModel.cs index d977515..195345c 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IElementViewModel.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IElementViewModel.cs @@ -1,6 +1,9 @@ +using FileTime.Core.Models; +using InitableService; + namespace FileTime.App.Core.ViewModels { - public interface IElementViewModel : IItemViewModel + public interface IElementViewModel : IItemViewModel, IInitable { long? Size { get; set; } } diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IFileViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IFileViewModel.cs index 98656b2..1007202 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IFileViewModel.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IFileViewModel.cs @@ -1,6 +1,9 @@ +using FileTime.Core.Models; +using InitableService; + namespace FileTime.App.Core.ViewModels { - public interface IFileViewModel : IElementViewModel + public interface IFileViewModel : IElementViewModel, IInitable { } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IItemViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IItemViewModel.cs index 86afcc8..e9b9d30 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IItemViewModel.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IItemViewModel.cs @@ -2,13 +2,15 @@ using System.Reactive.Subjects; using FileTime.App.Core.Models; using FileTime.App.Core.Models.Enums; using FileTime.Core.Models; +using InitableService; namespace FileTime.App.Core.ViewModels { - public interface IItemViewModel + public interface IItemViewModel : IInitable { IItem? BaseItem { get; set; } IObservable>? DisplayName { get; set; } + string? DisplayNameText { get; set; } IObservable? IsSelected { get; set; } IObservable? IsMarked { get; set; } BehaviorSubject IsAlternative { get; } diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs index ed528e0..7a5b9db 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs @@ -5,12 +5,14 @@ using InitableService; namespace FileTime.App.Core.ViewModels { - public interface ITabViewModel : IInitable + public interface ITabViewModel : IInitable { + ITab? Tab { get; } + int TabNumber { get; } + IObservable IsSelected { get; } IObservable? CurrentLocation { get; } IObservable? CurrentSelectedItem { get; } IObservable>? CurrentItems { get; } IObservable> MarkedItems { get; } - ITab? Tab { get; } } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/AppStateBase.cs b/src/AppCommon/FileTime.App.Core/AppStateBase.cs index 6ec81aa..e70879d 100644 --- a/src/AppCommon/FileTime.App.Core/AppStateBase.cs +++ b/src/AppCommon/FileTime.App.Core/AppStateBase.cs @@ -10,15 +10,31 @@ namespace FileTime.App.Core public abstract partial class AppStateBase : IAppState { private readonly BehaviorSubject _searchText = new(null); + private readonly BehaviorSubject _selectedTabObservable = new(null); + private ITabViewModel? _selectedTab; + public ObservableCollection Tabs { get; } = new(); public IObservable SearchText { get; private set; } - [Property] - private ITabViewModel? _selectedTab; + public IObservable SelectedTabObservable { get; private set; } + public ITabViewModel? SelectedTab + { + get => _selectedTab; + private set + { + if (value != _selectedTab) + { + _selectedTab = value; + OnPropertyChanged(nameof(SelectedTab)); + _selectedTabObservable.OnNext(value); + } + } + } partial void OnInitialize() { SearchText = _searchText.AsObservable(); + SelectedTabObservable = _selectedTabObservable.AsObservable(); } public void AddTab(ITabViewModel tabViewModel) @@ -45,5 +61,10 @@ namespace FileTime.App.Core { _searchText.OnNext(searchText); } + + public void SetSelectedTab(ITabViewModel tabToSelect) + { + SelectedTab = tabToSelect; + } } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/ContainerSizeContainerViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/ContainerSizeContainerViewModel.cs index 527e6a5..cf5b60d 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/ContainerSizeContainerViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/ContainerSizeContainerViewModel.cs @@ -1,11 +1,22 @@ +using FileTime.App.Core.Services; +using FileTime.Core.Models; using MvvmGen; namespace FileTime.App.Core.ViewModels { - [ViewModel] + [ViewModel(GenerateConstructor = false)] public partial class ContainerSizeContainerViewModel : ItemViewModel, IContainerSizeContainerViewModel { [Property] 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); + } } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/ContainerViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/ContainerViewModel.cs index 9b3de7e..105e6cd 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/ContainerViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/ContainerViewModel.cs @@ -1,6 +1,19 @@ +using FileTime.App.Core.Services; +using FileTime.Core.Models; +using MvvmGen; + 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); + } } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/ElementViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/ElementViewModel.cs index 60dbfdb..4482328 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/ElementViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/ElementViewModel.cs @@ -1,11 +1,22 @@ +using FileTime.App.Core.Services; +using FileTime.Core.Models; using MvvmGen; namespace FileTime.App.Core.ViewModels { - [ViewModel] + [ViewModel(GenerateConstructor = false)] public partial class ElementViewModel : ItemViewModel, IElementViewModel { [Property] 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); + } } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/FileViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/FileViewModel.cs index 60e3197..b6adff2 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/FileViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/FileViewModel.cs @@ -1,6 +1,19 @@ +using FileTime.App.Core.Services; +using FileTime.Core.Models; +using MvvmGen; + 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); + } } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/ItemViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/ItemViewModel.cs index 8751aeb..46f5605 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/ItemViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/ItemViewModel.cs @@ -1,12 +1,16 @@ +using System.Reactive.Linq; using System.Reactive.Subjects; using FileTime.App.Core.Models; using FileTime.App.Core.Models.Enums; +using FileTime.App.Core.Services; using FileTime.Core.Models; using MvvmGen; namespace FileTime.App.Core.ViewModels { [ViewModel] + [Inject(typeof(IAppState), "_appState")] + [Inject(typeof(IItemNameConverterService), "_itemNameConverterService")] public abstract partial class ItemViewModel : IItemViewModel { [Property] @@ -15,6 +19,9 @@ namespace FileTime.App.Core.ViewModels [Property] private IObservable>? _displayName; + [Property] + private string? _displayNameText; + [Property] private IObservable? _isSelected; @@ -32,5 +39,29 @@ namespace FileTime.App.Core.ViewModels [Property] private BehaviorSubject _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 + }; } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs index 164c0e9..389c00c 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs @@ -17,13 +17,16 @@ namespace FileTime.App.Core.ViewModels private readonly List _disposables = new(); private bool disposed; + public ITab? Tab { get; private set; } + public int TabNumber { get; private set; } + + public IObservable IsSelected { get; } + public IObservable? CurrentLocation { get; private set; } public IObservable? CurrentSelectedItem { get; private set; } public IObservable>? CurrentItems { get; private set; } public IObservable> MarkedItems { get; } - public ITab? Tab { get; private set; } - public TabViewModel( IServiceProvider serviceProvider, IItemNameConverterService itemNameConverterService, @@ -34,10 +37,14 @@ namespace FileTime.App.Core.ViewModels _appState = appState; 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(); CurrentItems = tab.CurrentItems.Select(items => items.Select(MapItemToViewModel).ToList()); CurrentSelectedItem = Observable.CombineLatest( @@ -45,61 +52,33 @@ namespace FileTime.App.Core.ViewModels tab.CurrentSelectedItem, (currentItems, currentSelectedItemPath) => currentItems.FirstOrDefault(i => i.BaseItem?.FullName == currentSelectedItemPath?.Path)); tab.CurrentLocation.Subscribe((_) => _markedItems.OnNext(Enumerable.Empty())); - - Tab = tab; } private IItemViewModel MapItemToViewModel(IItem item, int index) { if (item is IContainer container) { - var containerViewModel = _serviceProvider.GetRequiredService(); - InitIItemViewModel(containerViewModel, item); + var containerViewModel = _serviceProvider.GetInitableResolver(container, this, index).GetRequiredService(); return containerViewModel; } else if (item is IFileElement fileElement) { - var fileViewModel = _serviceProvider.GetRequiredService(); - InitIItemViewModel(fileViewModel, item); + var fileViewModel = _serviceProvider.GetInitableResolver(fileElement, this, index).GetRequiredService(); fileViewModel.Size = fileElement.Size; return fileViewModel; } else if (item is IElement element) { - var elementViewModel = _serviceProvider.GetRequiredService(); - InitIItemViewModel(elementViewModel, item); + var elementViewModel = _serviceProvider.GetInitableResolver(element, this, index).GetRequiredService(); return elementViewModel; } 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); public void Dispose() diff --git a/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs b/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs index e5922a7..0a59313 100644 --- a/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs +++ b/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs @@ -2,6 +2,7 @@ namespace FileTime.Core.Models { public interface IContainer : IItem { - IReadOnlyList Items { get; } + IObservable> Items { get; } + IObservable IsLoading { get; } } } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Models/Container.cs b/src/Core/FileTime.Core.Models/Container.cs index 7c80911..2c9cc7b 100644 --- a/src/Core/FileTime.Core.Models/Container.cs +++ b/src/Core/FileTime.Core.Models/Container.cs @@ -1,3 +1,5 @@ +using System.Reactive.Linq; +using System.Reactive.Subjects; using FileTime.Core.Enums; using FileTime.Core.Services; @@ -16,5 +18,9 @@ namespace FileTime.Core.Models bool CanRename, string? Attributes, IContentProvider Provider, - IReadOnlyList Items) : IContainer; + IObservable> Items) : IContainer + { + BehaviorSubject IsLoading { get; } = new BehaviorSubject(false); + IObservable IContainer.IsLoading => IsLoading.AsObservable(); + } } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Services/ContentProviderBase.cs b/src/Core/FileTime.Core.Services/ContentProviderBase.cs index e89dabc..5a658fc 100644 --- a/src/Core/FileTime.Core.Services/ContentProviderBase.cs +++ b/src/Core/FileTime.Core.Services/ContentProviderBase.cs @@ -1,3 +1,5 @@ +using System.Reactive.Linq; +using System.Reactive.Subjects; using FileTime.Core.Enums; using FileTime.Core.Models; @@ -5,9 +7,9 @@ namespace FileTime.Core.Services { public abstract class ContentProviderBase : IContentProvider { - protected List Items { get; set; } = new List(); + protected BehaviorSubject> Items { get; } = new BehaviorSubject>(new List()); - IReadOnlyList IContainer.Items => Items; + IObservable> IContainer.Items => Items; public string Name { get; } @@ -33,6 +35,10 @@ namespace FileTime.Core.Services public string? Attributes => null; + protected BehaviorSubject IsLoading { get; } = new(false); + + IObservable IContainer.IsLoading => IsLoading.AsObservable(); + protected ContentProviderBase(string name) { DisplayName = Name = name; diff --git a/src/Core/FileTime.Core.Services/Tab.cs b/src/Core/FileTime.Core.Services/Tab.cs index e16a610..280e13b 100644 --- a/src/Core/FileTime.Core.Services/Tab.cs +++ b/src/Core/FileTime.Core.Services/Tab.cs @@ -15,31 +15,38 @@ namespace FileTime.Core.Services public Tab() { CurrentLocation = _currentLocation.AsObservable(); - CurrentItems = _currentLocation - .Select(c => - Observable.FromAsync(async () => - c == null - ? Enumerable.Empty() - : await c.Items - .ToAsyncEnumerable() - .SelectAwait( - async i => - { - try + CurrentItems = + Observable.Merge( + _currentLocation + .Where(c => c is not null) + .Select(c => c!.Items) + .Switch() + .Select( + i => Observable.FromAsync(async () => + await i + .ToAsyncEnumerable() + .SelectAwait( + async i => { - //TODO: force create by AbsolutePath name - return await i.ContentProvider.GetItemByFullNameAsync(i.Path); + try + { + //TODO: force create by AbsolutePath name + return await i.ContentProvider.GetItemByFullNameAsync(i.Path); + } + catch { return null!; } } - catch { return null!; } - } - + ) + .Where(i => i != null) + .ToListAsync() ) - .Where(i => i != null) - .ToListAsync() - ) - ) - .Merge(Constants.MaximumObservableMergeOperations); - CurrentSelectedItem = CurrentLocation.Select(GetSelectedItemByLocation).Merge(_currentSelectedItem).Throttle(TimeSpan.FromMilliseconds(500)); + ) + .Merge(Constants.MaximumObservableMergeOperations), + _currentLocation + .Where(c => c is null) + .Select(c => Enumerable.Empty()) + ); + + CurrentSelectedItem = CurrentLocation.Select(GetSelectedItemByLocation).Switch().Merge(_currentSelectedItem).Throttle(TimeSpan.FromMilliseconds(500)); } public void Init(IContainer currentLocation) @@ -47,9 +54,9 @@ namespace FileTime.Core.Services _currentLocation.OnNext(currentLocation); } - private IAbsolutePath? GetSelectedItemByLocation(IContainer? currentLocation) + private IObservable GetSelectedItemByLocation(IContainer? currentLocation) { - return currentLocation?.Items[0]; + return currentLocation?.Items?.Select(i => i.FirstOrDefault()) ?? Observable.Never((IAbsolutePath?)null); } public void ChangeLocation(IContainer newLocation) diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml index 6d07da0..e5a3e94 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml @@ -132,6 +132,9 @@ + + + diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App/FileTime.GuiApp.App.csproj b/src/GuiApp/Avalonia/FileTime.GuiApp.App/FileTime.GuiApp.App.csproj index 5c260d9..d257293 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App/FileTime.GuiApp.App.csproj +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App/FileTime.GuiApp.App.csproj @@ -7,6 +7,7 @@ copyused true filetime.ico + 0.0.1 diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Assets/loading.svg b/src/GuiApp/Avalonia/FileTime.GuiApp/Assets/loading.svg new file mode 100644 index 0000000..5f0594a --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Assets/loading.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/CompareConverter.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/CompareConverter.cs new file mode 100644 index 0000000..ba7a427 --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/CompareConverter.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/SplitStringConverter.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/SplitStringConverter.cs new file mode 100644 index 0000000..debe186 --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/SplitStringConverter.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/FileTime.GuiApp.csproj b/src/GuiApp/Avalonia/FileTime.GuiApp/FileTime.GuiApp.csproj index 0325777..251b33b 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/FileTime.GuiApp.csproj +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/FileTime.GuiApp.csproj @@ -13,16 +13,6 @@ - - - MSBuild:Compile - - - - - MainWindow.axaml - - @@ -30,6 +20,7 @@ + diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs index 4d8491a..8151fa4 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; using Avalonia.Input; using FileTime.App.Core; using FileTime.App.Core.ViewModels; @@ -6,6 +7,7 @@ using FileTime.Core.Models; using FileTime.Core.Services; using FileTime.Providers.Local; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using MvvmGen; namespace FileTime.GuiApp.ViewModels @@ -14,17 +16,34 @@ namespace FileTime.GuiApp.ViewModels [Inject(typeof(IAppState), "_appState")] [Inject(typeof(ILocalContentProvider), "_localContentProvider")] [Inject(typeof(IServiceProvider), PropertyName = "_serviceProvider")] + [Inject(typeof(ILogger), PropertyName = "_logger")] public partial class MainWindowViewModel : IMainWindowViewModelBase { public bool Loading => false; public IAppState AppState => _appState; + public string Title { get; private set; } 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) { var tab = _serviceProvider.GetInitableResolver(_localContentProvider).GetRequiredService(); - var tabViewModel = _serviceProvider.GetInitableResolver(tab).GetRequiredService(); + var tabViewModel = _serviceProvider.GetInitableResolver(tab, 1).GetRequiredService(); _appState.AddTab(tabViewModel); } diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml index 22e81ca..5c858bb 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml @@ -23,22 +23,116 @@ - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Empty + + + + + + + + diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml.cs index dd572b0..3e8a70d 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml.cs @@ -49,5 +49,24 @@ namespace FileTime.GuiApp.Views 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); + } + } } } \ No newline at end of file diff --git a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs index 25a3aaf..a83f4c3 100644 --- a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs +++ b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs @@ -1,3 +1,4 @@ +using System.Reactive.Linq; using System.Runtime.InteropServices; using FileTime.Core.Enums; using FileTime.Core.Models; @@ -27,8 +28,7 @@ namespace FileTime.Providers.Local ? new DirectoryInfo("/").GetDirectories() : Environment.GetLogicalDrives().Select(d => new DirectoryInfo(d)); - Items.Clear(); - Items.AddRange(rootDirectories.Select(DirectoryToAbsolutePath)); + Items.OnNext(rootDirectories.Select(DirectoryToAbsolutePath).ToList()); } public override Task GetItemByNativePathAsync(NativePath nativePath) @@ -78,7 +78,7 @@ namespace FileTime.Providers.Local true, GetDirectoryAttributes(directoryInfo), this, - GetItemsByContainer(directoryInfo) + Observable.Return(GetItemsByContainer(directoryInfo)) ); }