Use DynamicData for collection management
This commit is contained in:
Родитель
e04bdf6b2a
Коммит
a8e6de3f84
|
@ -60,10 +60,10 @@
|
|||
Content="Refresh"
|
||||
Margin="12" />
|
||||
</StackPanel>
|
||||
<ProgressBar Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
IsIndeterminate="True"
|
||||
<ProgressBar Grid.Row="1"
|
||||
Grid.RowSpan="2"
|
||||
Orientation="Vertical"
|
||||
IsIndeterminate="{Binding IsLoading}"
|
||||
IsVisible="{Binding IsLoading}" />
|
||||
</Grid>
|
||||
<Grid Grid.Column="1">
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
using System.Linq;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
using Camelotia.Presentation.Interfaces;
|
||||
using Camelotia.Presentation.ViewModels;
|
||||
using Camelotia.Services.Interfaces;
|
||||
using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Reactive.Testing;
|
||||
using NSubstitute;
|
||||
|
@ -20,9 +22,9 @@ namespace Camelotia.Presentation.Tests
|
|||
public void ShouldIndicateWhenLoadingAndReady() => new TestScheduler().With(scheduler =>
|
||||
{
|
||||
_providerStorage
|
||||
.LoadProviders()
|
||||
.Returns(Enumerable.Empty<IProvider>());
|
||||
|
||||
.Connect()
|
||||
.Returns(Observable.Return(new ChangeSet<IProvider>()));
|
||||
|
||||
var model = BuildMainViewModel(scheduler);
|
||||
model.IsLoading.Should().BeFalse();
|
||||
model.IsReady.Should().BeFalse();
|
||||
|
@ -44,10 +46,13 @@ namespace Camelotia.Presentation.Tests
|
|||
[Fact]
|
||||
public void ShouldSelectFirstProviderWhenProvidersGetLoaded() => new TestScheduler().With(scheduler =>
|
||||
{
|
||||
var providers = Enumerable.Repeat(Substitute.For<IProvider>(), 1);
|
||||
var collection = new ObservableCollectionExtended<IProvider>();
|
||||
var set = collection.ToObservableChangeSet();
|
||||
|
||||
_providerStorage.Connect().Returns(set);
|
||||
_providerStorage
|
||||
.LoadProviders()
|
||||
.Returns(providers);
|
||||
.When(storage => storage.LoadProviders())
|
||||
.Do(args => collection.Add(Substitute.For<IProvider>()));
|
||||
|
||||
var model = BuildMainViewModel(scheduler);
|
||||
scheduler.AdvanceBy(2);
|
||||
|
@ -63,10 +68,13 @@ namespace Camelotia.Presentation.Tests
|
|||
[Fact]
|
||||
public void ActivationShouldTriggerLoad() => new TestScheduler().With(scheduler =>
|
||||
{
|
||||
var providers = Enumerable.Repeat(Substitute.For<IProvider>(), 1);
|
||||
var collection = new ObservableCollectionExtended<IProvider>();
|
||||
var set = collection.ToObservableChangeSet();
|
||||
|
||||
_providerStorage.Connect().Returns(set);
|
||||
_providerStorage
|
||||
.LoadProviders()
|
||||
.Returns(providers);
|
||||
.When(storage => storage.LoadProviders())
|
||||
.Do(args => collection.Add(Substitute.For<IProvider>()));
|
||||
|
||||
var model = BuildMainViewModel(scheduler);
|
||||
scheduler.AdvanceBy(2);
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Camelotia.Services.Interfaces;
|
||||
using Camelotia.Services.Providers;
|
||||
using Camelotia.Services.Storages;
|
||||
using DynamicData;
|
||||
using DynamicData.Tests;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
|
@ -22,24 +25,26 @@ namespace Camelotia.Presentation.Tests
|
|||
new YandexFileSystemProvider(_authenticator, _tokenStorage)
|
||||
);
|
||||
|
||||
var sequence = await provider.LoadProviders();
|
||||
var providers = sequence.ToList();
|
||||
await provider.LoadProviders();
|
||||
var providers = provider.Connect().AsAggregator();
|
||||
|
||||
Assert.Contains(providers, x => x is LocalFileSystemProvider);
|
||||
Assert.Contains(providers, x => x is VkontakteFileSystemProvider);
|
||||
Assert.Contains(providers, x => x is YandexFileSystemProvider);
|
||||
Assert.Equal(3, providers.Data.Count);
|
||||
Assert.Contains(providers.Data.Items, x => x is LocalFileSystemProvider);
|
||||
Assert.Contains(providers.Data.Items, x => x is VkontakteFileSystemProvider);
|
||||
Assert.Contains(providers.Data.Items, x => x is YandexFileSystemProvider);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ShouldResolveOnlySpecifiedProvidersIfNeeded()
|
||||
{
|
||||
var provider = new ProviderStorage(new LocalFileSystemProvider());
|
||||
var sequence = await provider.LoadProviders();
|
||||
var providers = sequence.ToList();
|
||||
await provider.LoadProviders();
|
||||
var providers = provider.Connect().AsAggregator();
|
||||
|
||||
Assert.Contains(providers, x => x is LocalFileSystemProvider);
|
||||
Assert.DoesNotContain(providers, x => x is VkontakteFileSystemProvider);
|
||||
Assert.DoesNotContain(providers, x => x is YandexFileSystemProvider);
|
||||
Assert.Equal(1, providers.Data.Count);
|
||||
Assert.Contains(providers.Data.Items, x => x is LocalFileSystemProvider);
|
||||
Assert.DoesNotContain(providers.Data.Items, x => x is VkontakteFileSystemProvider);
|
||||
Assert.DoesNotContain(providers.Data.Items, x => x is YandexFileSystemProvider);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,6 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="ReactiveUI" Version="9.8.23" />
|
||||
<PackageReference Include="ReactiveUI.Fody" Version="9.8.23" />
|
||||
<PackageReference Include="DynamicData" Version="6.7.1.2534" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Input;
|
||||
|
||||
|
@ -6,9 +6,9 @@ namespace Camelotia.Presentation.Interfaces
|
|||
{
|
||||
public interface IMainViewModel : INotifyPropertyChanged
|
||||
{
|
||||
IProviderViewModel SelectedProvider { get; set; }
|
||||
ReadOnlyObservableCollection<IProviderViewModel> Providers { get; }
|
||||
|
||||
IEnumerable<IProviderViewModel> Providers { get; }
|
||||
IProviderViewModel SelectedProvider { get; set; }
|
||||
|
||||
ICommand LoadProviders { get; }
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Concurrency;
|
||||
|
@ -16,10 +16,10 @@ namespace Camelotia.Presentation.ViewModels
|
|||
{
|
||||
public sealed class MainViewModel : ReactiveObject, IMainViewModel, ISupportsActivation
|
||||
{
|
||||
private readonly ObservableAsPropertyHelper<IEnumerable<IProviderViewModel>> _providers;
|
||||
private readonly ReactiveCommand<Unit, IEnumerable<IProvider>> _loadProviders;
|
||||
private readonly ReadOnlyObservableCollection<IProviderViewModel> _providers;
|
||||
private readonly ObservableAsPropertyHelper<bool> _isLoading;
|
||||
private readonly ObservableAsPropertyHelper<bool> _isReady;
|
||||
private readonly ReactiveCommand<Unit, Unit> _load;
|
||||
|
||||
public MainViewModel(
|
||||
Func<IProvider, IFileManager, IAuthViewModel, IProviderViewModel> providerFactory,
|
||||
|
@ -29,51 +29,54 @@ namespace Camelotia.Presentation.ViewModels
|
|||
IScheduler currentThread,
|
||||
IScheduler mainThread)
|
||||
{
|
||||
_loadProviders = ReactiveCommand.CreateFromTask(
|
||||
_load = ReactiveCommand.CreateFromTask(
|
||||
providerStorage.LoadProviders,
|
||||
outputScheduler: mainThread);
|
||||
|
||||
_providers = _loadProviders
|
||||
.Select(providers => providers
|
||||
.Select(x => providerFactory(x, fileManager, authFactory(x)))
|
||||
.ToList())
|
||||
var observableProviders = providerStorage.Connect();
|
||||
observableProviders
|
||||
.Transform(x => providerFactory(x, fileManager, authFactory(x)))
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.StartWithEmpty()
|
||||
.ToProperty(this, x => x.Providers, scheduler: currentThread);
|
||||
.Bind(out _providers)
|
||||
.Subscribe();
|
||||
|
||||
_isLoading = _loadProviders
|
||||
_isLoading = _load
|
||||
.IsExecuting
|
||||
.ToProperty(this, x => x.IsLoading, scheduler: currentThread);
|
||||
|
||||
_isReady = _loadProviders
|
||||
_isReady = _load
|
||||
.IsExecuting
|
||||
.Select(executing => !executing)
|
||||
.Skip(1)
|
||||
.Select(executing => !executing)
|
||||
.ToProperty(this, x => x.IsReady, scheduler: currentThread);
|
||||
|
||||
this.WhenAnyValue(x => x.Providers)
|
||||
.Where(providers => providers != null)
|
||||
.Select(providers => providers.FirstOrDefault())
|
||||
.Subscribe(x => SelectedProvider = x);
|
||||
|
||||
observableProviders
|
||||
.Take(1)
|
||||
.Where(changes => changes.Any())
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.Select(changes => Providers.FirstOrDefault())
|
||||
.Subscribe(change => SelectedProvider = change);
|
||||
|
||||
Activator = new ViewModelActivator();
|
||||
this.WhenActivated(disposable =>
|
||||
{
|
||||
_loadProviders.Execute()
|
||||
_load.Execute()
|
||||
.Subscribe(x => { })
|
||||
.DisposeWith(disposable);
|
||||
});
|
||||
}
|
||||
|
||||
public ReadOnlyObservableCollection<IProviderViewModel> Providers => _providers;
|
||||
|
||||
[Reactive] public IProviderViewModel SelectedProvider { get; set; }
|
||||
|
||||
public IEnumerable<IProviderViewModel> Providers => _providers.Value;
|
||||
|
||||
public ICommand LoadProviders => _loadProviders;
|
||||
|
||||
public bool IsLoading => _isLoading.Value;
|
||||
|
||||
public bool IsReady => _isReady.Value;
|
||||
|
||||
public ViewModelActivator Activator { get; }
|
||||
|
||||
public bool IsLoading => _isLoading.Value;
|
||||
|
||||
public ICommand LoadProviders => _load;
|
||||
|
||||
public bool IsReady => _isReady.Value;
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DynamicData" Version="6.7.1.2534" />
|
||||
<PackageReference Include="akavache" Version="6.2.3" />
|
||||
<PackageReference Include="FluentFTP" Version="19.2.2" />
|
||||
<PackageReference Include="Octokit" Version="0.32.0" />
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using DynamicData;
|
||||
|
||||
namespace Camelotia.Services.Interfaces
|
||||
{
|
||||
public interface IProviderStorage
|
||||
{
|
||||
Task<IEnumerable<IProvider>> LoadProviders();
|
||||
IObservable<IChangeSet<IProvider>> Connect();
|
||||
|
||||
Task LoadProviders();
|
||||
}
|
||||
}
|
|
@ -1,15 +1,25 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Camelotia.Services.Interfaces;
|
||||
using DynamicData;
|
||||
|
||||
namespace Camelotia.Services.Storages
|
||||
{
|
||||
public sealed class ProviderStorage : IProviderStorage
|
||||
{
|
||||
private readonly SourceList<IProvider> _connectable = new SourceList<IProvider>();
|
||||
private readonly IEnumerable<IProvider> _providers;
|
||||
|
||||
public ProviderStorage(params IProvider[] providers) => _providers = providers;
|
||||
|
||||
public Task<IEnumerable<IProvider>> LoadProviders() => Task.FromResult(_providers);
|
||||
public IObservable<IChangeSet<IProvider>> Connect() => _connectable.Connect();
|
||||
|
||||
public async Task LoadProviders()
|
||||
{
|
||||
_connectable.Clear();
|
||||
await Task.Delay(1000);
|
||||
_connectable.AddRange(_providers);
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче