Use DynamicData for collection management

This commit is contained in:
artyom 2019-02-12 23:03:57 +03:00
Родитель e04bdf6b2a
Коммит a8e6de3f84
9 изменённых файлов: 85 добавлений и 56 удалений

Просмотреть файл

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