Use IDeclarativeProperty instead of IObservable

This commit is contained in:
2023-08-14 17:55:55 +02:00
parent 4381f1b47a
commit 335433562a
8 changed files with 105 additions and 59 deletions

View File

@@ -1,11 +1,13 @@
using DeclarativeProperty;
namespace FileTime.App.Core.ViewModels.Timeline; namespace FileTime.App.Core.ViewModels.Timeline;
public interface ICommandTimeStateViewModel public interface ICommandTimeStateViewModel
{ {
IObservable<int> TotalProgress { get; } IDeclarativeProperty<int> TotalProgress { get; }
IObservable<int> CurrentProgress { get; } IDeclarativeProperty<int> CurrentProgress { get; }
IObservable<string> DisplayLabel { get; } IDeclarativeProperty<string> DisplayLabel { get; }
IObservable<string> DisplayDetailLabel { get; } IDeclarativeProperty<string> DisplayDetailLabel { get; }
IObservable<bool> IsSelected { get; } IDeclarativeProperty<bool> IsSelected { get; }
void Cancel(); void Cancel();
} }

View File

@@ -1,4 +1,5 @@
using System.Reactive.Subjects; using System.Reactive.Subjects;
using DeclarativeProperty;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
namespace FileTime.App.Core.ViewModels.Timeline; namespace FileTime.App.Core.ViewModels.Timeline;
@@ -6,13 +7,13 @@ namespace FileTime.App.Core.ViewModels.Timeline;
public class CommandTimeStateViewModel : ICommandTimeStateViewModel public class CommandTimeStateViewModel : ICommandTimeStateViewModel
{ {
private readonly CommandTimeState _commandTimeState; private readonly CommandTimeState _commandTimeState;
public IObservable<int> TotalProgress { get; } public IDeclarativeProperty<int> TotalProgress { get; }
public IObservable<int> CurrentProgress { get; } public IDeclarativeProperty<int> CurrentProgress { get; }
public IObservable<string> DisplayLabel { get; } public IDeclarativeProperty<string> DisplayLabel { get; }
public IObservable<string> DisplayDetailLabel { get; } public IDeclarativeProperty<string> DisplayDetailLabel { get; }
public IObservable<bool> IsSelected { get; } public IDeclarativeProperty<bool> IsSelected { get; }
public CommandTimeStateViewModel(CommandTimeState commandTimeState) public CommandTimeStateViewModel(CommandTimeState commandTimeState)
{ {
@@ -22,7 +23,7 @@ public class CommandTimeStateViewModel : ICommandTimeStateViewModel
TotalProgress = commandTimeState.Command.TotalProgress; TotalProgress = commandTimeState.Command.TotalProgress;
CurrentProgress = commandTimeState.Command.CurrentProgress; CurrentProgress = commandTimeState.Command.CurrentProgress;
//TODO //TODO
IsSelected = new BehaviorSubject<bool>(false); IsSelected = new DeclarativeProperty<bool>(false);
} }
public void Cancel() public void Cancel()

View File

@@ -1,13 +1,14 @@
using DeclarativeProperty;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
namespace FileTime.Core.Command; namespace FileTime.Core.Command;
public interface ICommand public interface ICommand
{ {
IObservable<string> DisplayLabel { get; } IDeclarativeProperty<string> DisplayLabel { get; }
IObservable<string> DisplayDetailLabel { get; } IDeclarativeProperty<string> DisplayDetailLabel { get; }
IObservable<int> TotalProgress { get; } IDeclarativeProperty<int> TotalProgress { get; }
IObservable<int> CurrentProgress { get; } IDeclarativeProperty<int> CurrentProgress { get; }
Task<CanCommandRun> CanRun(PointInTime currentTime); Task<CanCommandRun> CanRun(PointInTime currentTime);
Task<PointInTime> SimulateCommand(PointInTime currentTime); Task<PointInTime> SimulateCommand(PointInTime currentTime);

View File

@@ -1,20 +1,21 @@
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using DeclarativeProperty;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
namespace FileTime.Core.Command; namespace FileTime.Core.Command;
public abstract class CommandBase : ICommand public abstract class CommandBase : ICommand
{ {
private readonly BehaviorSubject<string> _displayLabel; private readonly DeclarativeProperty<string> _displayLabel;
private readonly BehaviorSubject<string> _displayDetailLabel; private readonly DeclarativeProperty<string> _displayDetailLabel;
private readonly BehaviorSubject<int> _totalProgress; private readonly DeclarativeProperty<int> _totalProgress;
private readonly BehaviorSubject<int> _currentProgress; private readonly DeclarativeProperty<int> _currentProgress;
public IObservable<string> DisplayLabel { get; } public IDeclarativeProperty<string> DisplayLabel { get; }
public IObservable<string> DisplayDetailLabel { get; } public IDeclarativeProperty<string> DisplayDetailLabel { get; }
public IObservable<int> TotalProgress { get; } public IDeclarativeProperty<int> TotalProgress { get; }
public IObservable<int> CurrentProgress { get; } public IDeclarativeProperty<int> CurrentProgress { get; }
protected CommandBase(string displayLabel = "", string displayDetailLabel = "", int totalProgress = 0, int currentProgress = 0) protected CommandBase(string displayLabel = "", string displayDetailLabel = "", int totalProgress = 0, int currentProgress = 0)
{ {
@@ -23,32 +24,32 @@ public abstract class CommandBase : ICommand
_totalProgress = new(totalProgress); _totalProgress = new(totalProgress);
_currentProgress = new(currentProgress); _currentProgress = new(currentProgress);
DisplayLabel = _displayLabel.AsObservable(); DisplayLabel = _displayLabel;
DisplayDetailLabel = _displayDetailLabel.AsObservable(); DisplayDetailLabel = _displayDetailLabel;
TotalProgress = _totalProgress.AsObservable(); TotalProgress = _totalProgress;
CurrentProgress = _currentProgress.AsObservable(); CurrentProgress = _currentProgress;
} }
public abstract Task<CanCommandRun> CanRun(PointInTime currentTime); public abstract Task<CanCommandRun> CanRun(PointInTime currentTime);
public abstract Task<PointInTime> SimulateCommand(PointInTime currentTime); public abstract Task<PointInTime> SimulateCommand(PointInTime currentTime);
public abstract void Cancel(); public abstract void Cancel();
protected void SetDisplayLabel(string? displayLabel) => _displayLabel.OnNext(displayLabel ?? string.Empty); protected async Task SetDisplayLabelAsync(string? displayLabel) => await _displayLabel.SetValue(displayLabel ?? string.Empty);
protected void SetDisplayDetailLabel(string? displayLabel) => _displayDetailLabel.OnNext(displayLabel ?? string.Empty); protected async Task SetDisplayDetailLabel(string? displayLabel) => await _displayDetailLabel.SetValue(displayLabel ?? string.Empty);
protected void SetTotalProgress(int totalProgress) => _totalProgress.OnNext(totalProgress); protected async Task SetTotalProgress(int totalProgress) => await _totalProgress.SetValue(totalProgress);
protected void SetCurrentProgress(int currentProgress) => _currentProgress.OnNext(currentProgress); protected async Task SetCurrentProgress(int currentProgress) => await _currentProgress.SetValue(currentProgress);
protected IDisposable TrackProgress(IEnumerable<OperationProgress> operationProgresses) => protected IDisposable TrackProgress(IEnumerable<OperationProgress> operationProgresses) =>
operationProgresses operationProgresses
.Select(op => op.Progress.Select(p => (Progress: p, TotalProgress: op.TotalCount))) .Select(op => op.Progress.Map(p => (Progress: p, TotalProgress: op.TotalCount)))
.CombineLatest() .CombineAll(data =>
.Select(data =>
{ {
var total = data.Sum(d => d.TotalProgress); var dataList = data.ToList();
if (total == 0) return 0; var total = dataList.Sum(d => d.TotalProgress);
return (int) (data.Sum(d => d.Progress) * 100 / total); if (total == 0) return Task.FromResult(0);
return Task.FromResult((int)(dataList.Sum(d => d.Progress) * 100 / total));
}) })
.Subscribe(SetTotalProgress); .Subscribe(async (p, _) => await SetTotalProgress(p));
} }

View File

@@ -17,7 +17,7 @@ public class CopyCommand : CommandBase, ITransportationCommand
private readonly CancellationTokenSource _cancellationTokenSource = new(); private readonly CancellationTokenSource _cancellationTokenSource = new();
private readonly List<OperationProgress> _operationProgresses = new(); private readonly List<OperationProgress> _operationProgresses = new();
private readonly BehaviorSubject<OperationProgress?> _currentOperationProgress = new(null); private readonly DeclarativeProperty<OperationProgress?> _currentOperationProgress = new();
private long _recentTotalSum; private long _recentTotalSum;
private readonly DeclarativeProperty<long> _recentTotalProcessed = new(); private readonly DeclarativeProperty<long> _recentTotalProcessed = new();
@@ -46,17 +46,16 @@ public class CopyCommand : CommandBase, ITransportationCommand
_commandSchedulerNotifier = commandSchedulerNotifier; _commandSchedulerNotifier = commandSchedulerNotifier;
_logger = logger; _logger = logger;
_currentOperationProgress _currentOperationProgress
.Select(p => .Map(p =>
{ {
if (p is null) return Observable.Never<int>(); return p?.Progress.Map(currentProgress =>
return p.Progress.Select(currentProgress =>
p.TotalCount == 0 p.TotalCount == 0
? 0 ? 0
: (int) (currentProgress * 100 / p.TotalCount) : (int) (currentProgress * 100 / p.TotalCount)
); );
}) })
.Switch() .Switch()
.Subscribe(SetCurrentProgress); .Subscribe(async (p, _) => await SetCurrentProgress(p));
Sources = new List<FullName>(sources).AsReadOnly(); Sources = new List<FullName>(sources).AsReadOnly();
TransportMode = mode; TransportMode = mode;
@@ -75,7 +74,7 @@ public class CopyCommand : CommandBase, ITransportationCommand
recentSpeed recentSpeed
.Debounce(TimeSpan.FromMilliseconds(500)) .Debounce(TimeSpan.FromMilliseconds(500))
.Subscribe(SetDisplayDetailLabel); .Subscribe(async (l, _) => await SetDisplayDetailLabel(l));
} }
public override Task<CanCommandRun> CanRun(PointInTime currentTime) public override Task<CanCommandRun> CanRun(PointInTime currentTime)
@@ -145,20 +144,21 @@ public class CopyCommand : CommandBase, ITransportationCommand
if (Sources.Count == 1) if (Sources.Count == 1)
{ {
SetDisplayLabel($"Copy - {Sources[0].GetName()}"); await SetDisplayLabelAsync($"Copy - {Sources[0].GetName()}");
} }
else else
{ {
_operationProgresses _operationProgresses
.Select(o => o.IsDone) .Select(o => o.IsDone)
.CombineLatest() .CombineAll(statuses =>
.Subscribe(statuses =>
{ {
var done = statuses.Count(s => s) + 1; var statusList = statuses.ToList();
if (done > statuses.Count) done = statuses.Count; var done = statusList.Count(s => s) + 1;
if (done > statusList.Count) done = statusList.Count;
SetDisplayLabel($"Copy - {done} / {statuses.Count}"); return Task.FromResult($"Copy - {done} / {statusList.Count}");
}); })
.Subscribe(async (v, _) => await SetDisplayLabelAsync(v));
} }
} }
@@ -196,7 +196,7 @@ public class CopyCommand : CommandBase, ITransportationCommand
var newElementPath = target.GetChild(newElementName, AbsolutePathType.Element); var newElementPath = target.GetChild(newElementName, AbsolutePathType.Element);
var currentProgress = _operationProgresses.Find(o => o.Key == element.FullName!.Path); var currentProgress = _operationProgresses.Find(o => o.Key == element.FullName!.Path);
_currentOperationProgress.OnNext(currentProgress); await _currentOperationProgress.SetValue(currentProgress);
await copyOperation.CopyAsync(new AbsolutePath(_timelessContentProvider, element), newElementPath, new CopyCommandContext(UpdateProgress, currentProgress, _cancellationTokenSource.Token)); await copyOperation.CopyAsync(new AbsolutePath(_timelessContentProvider, element), newElementPath, new CopyCommandContext(UpdateProgress, currentProgress, _cancellationTokenSource.Token));
} }

View File

@@ -21,7 +21,6 @@
x:DataType="vm:IMainWindowViewModelBase" x:DataType="vm:IMainWindowViewModelBase"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:appCoreModels="using:FileTime.App.Core.Models" xmlns:appCoreModels="using:FileTime.App.Core.Models"
xmlns:appInteractions="using:FileTime.App.Core.Interactions"
xmlns:corevm="using:FileTime.App.Core.ViewModels" xmlns:corevm="using:FileTime.App.Core.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity" xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
@@ -346,7 +345,7 @@
VerticalAlignment="Top"> VerticalAlignment="Top">
<Grid ColumnDefinitions="*, *" RowDefinitions="Auto, Auto"> <Grid ColumnDefinitions="*, *" RowDefinitions="Auto, Auto">
<Grid ColumnDefinitions="*, Auto" Grid.ColumnSpan="2"> <Grid ColumnDefinitions="*, Auto" Grid.ColumnSpan="2">
<TextBlock Text="{Binding DisplayLabel^}" /> <TextBlock Text="{Binding DisplayLabel.Value}" />
<Button Command="{Binding Cancel}" Grid.Column="1"> <Button Command="{Binding Cancel}" Grid.Column="1">
<Image <Image
@@ -362,25 +361,25 @@
Grid.Column="0" Grid.Column="0"
Grid.Row="1" Grid.Row="1"
Margin="0,5,5,5"> Margin="0,5,5,5">
<TextBlock Text="{Binding DisplayDetailLabel^}" TextAlignment="Right" /> <TextBlock Text="{Binding DisplayDetailLabel.Value}" TextAlignment="Right" />
<ProgressBar <ProgressBar
Margin="0,5,0,0" Margin="0,5,0,0"
Maximum="100" Maximum="100"
MinWidth="50" MinWidth="50"
Value="{Binding CurrentProgress^}" /> Value="{Binding CurrentProgress.Value}" />
</StackPanel> </StackPanel>
<StackPanel <StackPanel
Grid.Column="1" Grid.Column="1"
Grid.Row="1" Grid.Row="1"
Margin="5,5,0,5"> Margin="5,5,0,5">
<TextBlock Text="{Binding TotalProgress^, StringFormat={}{0}%}" TextAlignment="Right" /> <TextBlock Text="{Binding TotalProgress.Value, StringFormat={}{0}%}" TextAlignment="Right" />
<ProgressBar <ProgressBar
Margin="0,5,0,0" Margin="0,5,0,0"
Maximum="100" Maximum="100"
MinWidth="50" MinWidth="50"
Value="{Binding TotalProgress^}" /> Value="{Binding TotalProgress.Value}" />
</StackPanel> </StackPanel>
</Grid> </Grid>
</Border> </Border>

View File

@@ -0,0 +1,36 @@
namespace DeclarativeProperty;
public class CombineAllProperty<T, TResult> : DeclarativePropertyBase<TResult>
{
private readonly List<IDeclarativeProperty<T>> _sources;
private readonly Func<IEnumerable<T>, Task<TResult?>> _combiner;
public CombineAllProperty(
IEnumerable<IDeclarativeProperty<T>> sources,
Func<IEnumerable<T>, Task<TResult?>> combiner,
Action<TResult?>? setValueHook = null) : base(setValueHook)
{
var sourcesList = sources.ToList();
_sources = sourcesList;
_combiner = combiner;
var initialValueTask = _combiner(sourcesList.Select(p => p.Value));
initialValueTask.Wait();
SetNewValueSync(initialValueTask.Result);
foreach (var declarativeProperty in sourcesList)
{
AddDisposable(declarativeProperty.Subscribe(OnSourceChanged));
}
}
private async Task OnSourceChanged(T? arg1, CancellationToken arg2) => await Update();
private async Task Update()
{
var values = _sources.Select(p => p.Value);
var result = await _combiner(values);
await SetNewValueAsync(result);
}
}

View File

@@ -93,4 +93,10 @@ public static class DeclarativePropertyExtensions
public static IDeclarativeProperty<T> Switch<T>(this IDeclarativeProperty<IDeclarativeProperty<T>?> from) public static IDeclarativeProperty<T> Switch<T>(this IDeclarativeProperty<IDeclarativeProperty<T>?> from)
=> new SwitchProperty<T>(from); => new SwitchProperty<T>(from);
public static IDeclarativeProperty<TResult?> CombineAll<T, TResult>(
this IEnumerable<IDeclarativeProperty<T>> sources,
Func<IEnumerable<T>, Task<TResult>> combiner,
Action<TResult?>? setValueHook = null)
=> new CombineAllProperty<T,TResult?>(sources, combiner, setValueHook);
} }