Refactor to LineChartAnimatorViewModel
ADD
WalletWasabi.Fluent/ViewModels/Wallets/Home/Tiles/LineChartAnimatorViewModel.cs
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Avalonia.Animation.Easings;
+using Avalonia.Threading;
+using WalletWasabi.Fluent.Morph;
+
+namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles
+{
+ public partial class LineChartAnimatorViewModel : ViewModelBase
+ {
+ private readonly IEasing _animationEasing;
+ private readonly double _animationSpeed;
+ private DispatcherTimer? _timer;
+ [AutoNotify(SetterModifier = AccessModifier.Private)] private List<PolyLine>? _animationFrames;
+ [AutoNotify(SetterModifier = AccessModifier.Private)] private int _totalAnimationFrames;
+ [AutoNotify(SetterModifier = AccessModifier.Private)] private int _currentAnimationFrame;
+ [AutoNotify] private PolyLine? _source;
+ [AutoNotify] private PolyLine? _target;
+ [AutoNotify(SetterModifier = AccessModifier.Private)] private bool _isAnimationRunning;
+ [AutoNotify] private ObservableCollection<double> _yValues;
+ [AutoNotify] private ObservableCollection<double> _xValues;
+ [AutoNotify] private double? _xMinimum;
+ [AutoNotify] private List<string>? _yLabels;
+ [AutoNotify] private List<string>? _xLabels;
+
+ public LineChartAnimatorViewModel()
+ {
+ _animationEasing = new SplineEasing();
+ _animationSpeed = 0.05;
+ _isAnimationRunning = false;
+ _yValues = new ObservableCollection<double>();
+ _xValues = new ObservableCollection<double>();
+ }
+
+ private void CreateAnimation(PolyLine source, PolyLine target)
+ {
+ _totalAnimationFrames = (int)(1 / _animationSpeed);
+ _animationFrames = PolyLineMorph.ToCache(source, target, _animationSpeed, _animationEasing, interpolateXAxis: false);
+ _currentAnimationFrame = 0;
+ }
+
+ private void AnimationTimerOnTick(object? sender, EventArgs e)
+ {
+ if (_animationFrames is null)
+ {
+ return;
+ }
+
+ SetFrameValues(_animationFrames, _currentAnimationFrame);
+ _currentAnimationFrame++;
+
+ if (_currentAnimationFrame >= _totalAnimationFrames)
+ {
+ StopTimer();
+ }
+ }
+
+ private void SetFrameValues(List<PolyLine> frames, int count)
+ {
+ XValues = frames[count].XValues;
+ YValues = frames[count].YValues;
+ }
+
+ public void StartTimer()
+ {
+ if (_timer is null)
+ {
+ _timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1 / 60.0) };
+ _timer.Tick += AnimationTimerOnTick;
+ }
+
+ _timer?.Start();
+ IsAnimationRunning = true;
+ }
+
+ public void StopTimer()
+ {
+ _timer?.Stop();
+ IsAnimationRunning = false;
+ }
+
+ public void UpdateValues()
+ {
+ if (_source is null || _target is null)
+ {
+ return;
+ }
+
+ if (_source.XValues.Count > 0 && _target.XValues.Count > 0)
+ {
+ CreateAnimation(_source, _target);
+ StartTimer();
+ }
+ else
+ {
+ XValues = _target.XValues;
+ YValues = _target.YValues;
+ }
+ }
+ }
+}
\ No newline at end of file

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Home/Tiles/WalletBalanceChartTileViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Home/Tiles/WalletBalanceChartTileViewModel.cs
@@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Disposables;
-using Avalonia.Animation.Easings;
-using Avalonia.Threading;
using DynamicData.Binding;
using NBitcoin;
using WalletWasabi.Fluent.Helpers;
@@ -17,31 +15,13 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles
public partial class WalletBalanceChartTileViewModel : TileViewModel
{
private readonly ObservableCollection<HistoryItemViewModel> _history;
- private readonly IEasing _animationEasing;
- private readonly double _animationSpeed;
- private DispatcherTimer? _timer;
- private PolyLine? _source;
- private PolyLine? _target;
- private List<PolyLine>? _animationFrames;
- private int _totalAnimationFrames;
- private int _currentAnimationFrame;
- [AutoNotify] private ObservableCollection<double> _yValues;
- [AutoNotify] private ObservableCollection<double> _xValues;
- [AutoNotify] private double? _xMinimum;
- [AutoNotify] private List<string>? _yLabels;
- [AutoNotify] private List<string>? _xLabels;
- [AutoNotify(SetterModifier = AccessModifier.Private)] private bool _isAnimationRunning;
public WalletBalanceChartTileViewModel(ObservableCollection<HistoryItemViewModel> history)
{
_history = history;
- _animationEasing = new SplineEasing();
- _animationSpeed = 0.05;
- _isAnimationRunning = false;
+ Animator = new LineChartAnimatorViewModel();
- _yValues = new ObservableCollection<double>();
- _xValues = new ObservableCollection<double>();
TimePeriodOptions = new ObservableCollection<TimePeriodOptionViewModel>();
foreach (var item in (TimePeriodOption[]) Enum.GetValues(typeof(TimePeriodOption)))
@@ -53,6 +33,8 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles
}
}
+ public LineChartAnimatorViewModel Animator { get; }
+
public ObservableCollection<TimePeriodOptionViewModel> TimePeriodOptions { get; }
protected override void OnActivated(CompositeDisposable disposables)
@@ -113,66 +95,66 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles
private void UpdateSample(TimeSpan sampleTime, TimeSpan sampleBackFor)
{
- StopTimer();
+ Animator.StopTimer();
var sampleLimit = DateTimeOffset.Now - sampleBackFor;
- XMinimum = sampleLimit.ToUnixTimeMilliseconds();
-
- var values = _history.SelectTimeSampleBackwards(
- x => x.Date,
- x => x.Balance,
- sampleTime,
- sampleLimit,
- DateTime.Now);
+ Animator.XMinimum = sampleLimit.ToUnixTimeMilliseconds();
- var sourceXValues = XValues;
- var sourceYValues = YValues;
+ var sourceXValues = Animator.XValues;
+ var sourceYValues = Animator.YValues;
- if (_animationFrames is { })
+ if (Animator.AnimationFrames is { })
{
- if (_currentAnimationFrame < _totalAnimationFrames)
+ if (Animator.CurrentAnimationFrame < Animator.TotalAnimationFrames)
{
- sourceXValues = _animationFrames[_totalAnimationFrames - 1].XValues;
- sourceYValues = _animationFrames[_totalAnimationFrames - 1].YValues;
+ sourceXValues = Animator.AnimationFrames[Animator.TotalAnimationFrames - 1].XValues;
+ sourceYValues = Animator.AnimationFrames[Animator.TotalAnimationFrames - 1].YValues;
}
}
- _source = new PolyLine()
+ Animator.Source = new PolyLine()
{
XValues = new ObservableCollection<double>(sourceXValues),
YValues = new ObservableCollection<double>(sourceYValues)
};
- _target = new PolyLine()
+ Animator.Target = new PolyLine()
{
XValues = new ObservableCollection<double>(),
YValues = new ObservableCollection<double>()
};
- XValues.Clear();
- YValues.Clear();
+ Animator.XValues.Clear();
+ Animator.YValues.Clear();
+
+ var values = _history.SelectTimeSampleBackwards(
+ x => x.Date,
+ x => x.Balance,
+ sampleTime,
+ sampleLimit,
+ DateTime.Now);
foreach (var (timestamp, balance) in values.Reverse())
{
- _target.YValues.Add((double)balance.ToDecimal(MoneyUnit.BTC));
- _target.XValues.Add(timestamp.ToUnixTimeMilliseconds());
+ Animator.Target.YValues.Add((double)balance.ToDecimal(MoneyUnit.BTC));
+ Animator.Target.XValues.Add(timestamp.ToUnixTimeMilliseconds());
}
- if (_target.YValues.Any())
+ if (Animator.Target.YValues.Any())
{
- var maxY = _target.YValues.Max();
- YLabels = new List<string> { "0", (maxY / 2).ToString("F2"), maxY.ToString("F2") };
+ var maxY = Animator.Target.YValues.Max();
+ Animator.YLabels = new List<string> { "0", (maxY / 2).ToString("F2"), maxY.ToString("F2") };
}
else
{
- YLabels = null;
+ Animator.YLabels = null;
}
- if (_target.XValues.Any())
+ if (Animator.Target.XValues.Any())
{
- var minX = _target.XValues.Min();
- var maxX = _target.XValues.Max();
+ var minX = Animator.Target.XValues.Min();
+ var maxX = Animator.Target.XValues.Max();
var halfX = minX + ((maxX - minX) / 2);
var range = DateTimeOffset.FromUnixTimeMilliseconds((long)maxX) -
@@ -180,7 +162,7 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles
if (range <= TimeSpan.FromDays(1))
{
- XLabels = new List<string>
+ Animator.XLabels = new List<string>
{
DateTimeOffset.FromUnixTimeMilliseconds((long) minX).DateTime.ToString("t"),
DateTimeOffset.FromUnixTimeMilliseconds((long) halfX).DateTime.ToString("t"),
@@ -189,7 +171,7 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles
}
else if (range <= TimeSpan.FromDays(7))
{
- XLabels = new List<string>
+ Animator.XLabels = new List<string>
{
DateTimeOffset.FromUnixTimeMilliseconds((long) minX).DateTime.ToString("ddd MMM-d"),
DateTimeOffset.FromUnixTimeMilliseconds((long) halfX).DateTime.ToString("ddd MMM-d"),
@@ -198,7 +180,7 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles
}
else
{
- XLabels = new List<string>
+ Animator.XLabels = new List<string>
{
DateTimeOffset.FromUnixTimeMilliseconds((long) minX).DateTime.ToString("MMM-d"),
DateTimeOffset.FromUnixTimeMilliseconds((long) halfX).DateTime.ToString("MMM-d"),
@@ -208,71 +190,10 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles
}
else
{
- XLabels = null;
+ Animator.XLabels = null;
}
- UpdateValues(_source, _target);
- }
-
- private void UpdateValues(PolyLine source, PolyLine target)
- {
- if (source.XValues.Count > 0 && target.XValues.Count > 0)
- {
- CreateAnimation(source, target);
- StartTimer();
- }
- else
- {
- XValues = target.XValues;
- YValues = target.YValues;
- }
- }
-
- private void CreateAnimation(PolyLine source, PolyLine target)
- {
- _totalAnimationFrames = (int)(1 / _animationSpeed);
- _animationFrames = PolyLineMorph.ToCache(source, target, _animationSpeed, _animationEasing, interpolateXAxis: false);
- _currentAnimationFrame = 0;
- }
-
- private void StartTimer()
- {
- if (_timer is null)
- {
- _timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1 / 60.0) };
- _timer.Tick += AnimationTimerOnTick;
- }
-
- _timer?.Start();
- IsAnimationRunning = true;
- }
-
- private void StopTimer()
- {
- _timer?.Stop();
- IsAnimationRunning = false;
- }
-
- private void AnimationTimerOnTick(object? sender, EventArgs e)
- {
- if (_animationFrames is null)
- {
- return;
- }
-
- SetFrameValues(_animationFrames, _currentAnimationFrame);
- _currentAnimationFrame++;
-
- if (_currentAnimationFrame >= _totalAnimationFrames)
- {
- StopTimer();
- }
- }
-
- private void SetFrameValues(List<PolyLine> frames, int count)
- {
- XValues = frames[count].XValues;
- YValues = frames[count].YValues;
+ Animator.UpdateValues();
}
}
}

MODIFY
WalletWasabi.Fluent/Views/Wallets/Home/Tiles/WalletBalanceChart/WalletBalanceChartLargeTileView.axaml
WalletWasabi.Fluent/Views/Wallets/Home/Tiles/WalletBalanceChart/WalletBalanceChartLargeTileView.axaml
<Separator DockPanel.Dock="Top" Margin="-16 15" />
<c:PrivacyContentControl DockPanel.Dock="Bottom" PrivacyReplacementMode="Icon">
- <c:LineChart x:CompileBindings="True" x:DataType="vm:WalletBalanceChartTileViewModel" x:Name="GradientLineChart"
+ <c:LineChart x:CompileBindings="True"
+ DataContext="{Binding Animator}"
Classes="area border xAxis xAxisLabels yAxis yAxisLabels"
Margin="30 10 15 30"
SmoothCurve="True"

MODIFY
WalletWasabi.Fluent/Views/Wallets/Home/Tiles/WalletBalanceChart/WalletBalanceChartMediumTileView.axaml
WalletWasabi.Fluent/Views/Wallets/Home/Tiles/WalletBalanceChart/WalletBalanceChartMediumTileView.axaml
</Button>
</DockPanel>
<c:PrivacyContentControl PrivacyReplacementMode="Icon">
- <c:LineChart x:CompileBindings="True" x:DataType="vm:WalletBalanceChartTileViewModel" x:Name="GradientLineChart"
+ <c:LineChart x:CompileBindings="True"
+ DataContext="{Binding Animator}"
Classes="area border xAxis xAxisLabels yAxis yAxisLabels"
Margin="30 15 15 25"
SmoothCurve="True"

MODIFY
WalletWasabi.Fluent/Views/Wallets/Home/Tiles/WalletBalanceChart/WalletBalanceChartWideTileView.axaml
WalletWasabi.Fluent/Views/Wallets/Home/Tiles/WalletBalanceChart/WalletBalanceChartWideTileView.axaml
<Separator DockPanel.Dock="Top" Margin="-16 15" />
<c:PrivacyContentControl DockPanel.Dock="Bottom" PrivacyReplacementMode="Icon">
- <c:LineChart x:CompileBindings="True" x:DataType="vm:WalletBalanceChartTileViewModel" x:Name="GradientLineChart"
+ <c:LineChart x:CompileBindings="True"
+ DataContext="{Binding Animator}"
Classes="area border xAxis xAxisLabels yAxis yAxisLabels"
Margin="30 5 15 30"
SmoothCurve="True"

Refactor to LineChartAnimatorViewModel