PrivacyRing and PrivacyBar render threshold
MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Home/Tiles/PrivacyRing/PrivacyRingItemViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Home/Tiles/PrivacyRing/PrivacyRingItemViewModel.cs
using Avalonia;
using Avalonia.Media;
+using System.Linq;
using WalletWasabi.Blockchain.TransactionOutputs;
using WalletWasabi.Fluent.Helpers;
+using WalletWasabi.Fluent.Models;
using WalletWasabi.Fluent.ViewModels.Wallets.Advanced.WalletCoins;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles.PrivacyRing;
-public class PrivacyRingItemViewModel : WalletCoinViewModel, IPrivacyRingPreviewItem
+public class PrivacyRingItemViewModel : IPrivacyRingPreviewItem, IDisposable
{
private const double TotalAngle = 2d * Math.PI;
private const double UprightAngle = Math.PI / 2d;
private const double SegmentWidth = 10.0;
- public PrivacyRingItemViewModel(PrivacyRingViewModel parent, SmartCoin coin, double start, double end) : base(coin)
+ public PrivacyRingItemViewModel(PrivacyRingViewModel parent, SmartCoin coin, double start, double end)
+ {
+ Coin = new WalletCoinViewModel(coin);
+ OuterRadius = Math.Min(parent.Height / 2, parent.Width / 2);
+
+ Data = CreateGeometry(start, end, OuterRadius);
+
+ IsPrivate = coin.IsPrivate(parent.Wallet.KeyManager.AnonScoreTarget);
+ IsSemiPrivate = !IsPrivate && coin.IsSemiPrivate();
+ IsNonPrivate = !IsPrivate && !IsSemiPrivate;
+ AmountText = $"{Coin.Amount.ToFormattedString()} BTC";
+ Unconfirmed = !coin.Confirmed;
+ Confirmations = coin.GetConfirmations();
+ }
+
+ public PrivacyRingItemViewModel(PrivacyRingViewModel parent, Pocket pocket, double start, double end)
+ {
+ OuterRadius = Math.Min(parent.Height / 2, parent.Width / 2);
+
+ Data = CreateGeometry(start, end, OuterRadius);
+
+ IsPrivate = pocket.Coins.First().IsPrivate(parent.Wallet.KeyManager.AnonScoreTarget);
+ IsSemiPrivate = !IsPrivate && pocket.Coins.First().IsSemiPrivate();
+ IsNonPrivate = !IsPrivate && !IsSemiPrivate;
+ AmountText = $"{pocket.Amount.ToFormattedString()} BTC";
+ Unconfirmed = false;
+ }
+
+ private PathGeometry CreateGeometry(double start, double end, double outerRadius)
{
- var outerRadius = Math.Min(parent.Height / 2, parent.Width / 2);
var innerRadius = outerRadius - SegmentWidth;
var arc1Size = new Size(outerRadius, outerRadius);
@@ -45,15 +74,7 @@ public class PrivacyRingItemViewModel : WalletCoinViewModel, IPrivacyRingPreview
var origin2 = GetAnglePoint(innerRadius, endAngle - innerOffset);
var arc2 = GetAnglePoint(innerRadius, startAngle + innerOffset);
- OuterRadius = outerRadius;
- IsPrivate = coin.IsPrivate(parent.Wallet.KeyManager.AnonScoreTarget);
- IsSemiPrivate = !IsPrivate && coin.IsSemiPrivate();
- IsNonPrivate = !IsPrivate && !IsSemiPrivate;
- AmountText = $"{Amount.ToFormattedString()} BTC";
- Unconfirmed = !coin.Confirmed;
- Confirmations = coin.GetConfirmations();
-
- Data = new PathGeometry()
+ return new PathGeometry()
{
Figures =
{
@@ -71,9 +92,11 @@ public class PrivacyRingItemViewModel : WalletCoinViewModel, IPrivacyRingPreview
};
}
- public PathGeometry Data { get; }
+ public WalletCoinViewModel? Coin { get; }
+
+ public PathGeometry Data { get; private set; }
- public double OuterRadius { get; }
+ public double OuterRadius { get; private set; }
public bool IsPrivate { get; }
public bool IsSemiPrivate { get; }
@@ -88,4 +111,9 @@ public class PrivacyRingItemViewModel : WalletCoinViewModel, IPrivacyRingPreview
var y = r * Math.Sin(angle);
return new Point(x, y);
}
+
+ public void Dispose()
+ {
+ Coin?.Dispose();
+ }
}

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Home/Tiles/PrivacyRing/PrivacyRingViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Home/Tiles/PrivacyRing/PrivacyRingViewModel.cs
@@ -81,10 +81,42 @@ public partial class PrivacyRingViewModel : RoutableViewModel
private void RefreshCoinsList(IEnumerable<Pocket> pockets)
{
- _itemsSourceList.Edit(list =>
+ _itemsSourceList.Edit(list => CreateSegments(pockets, list));
+ }
+
+ private void CreateSegments(IEnumerable<Pocket> pockets, IExtendedList<PrivacyRingItemViewModel> list)
{
list.Clear();
+ if (Width == 0d)
+ {
+ return;
+ }
+
+ var coinCount = pockets.SelectMany(x => x.Coins).Count();
+
+ var result = Enumerable.Empty<PrivacyRingItemViewModel>();
+
+ if (coinCount < UIConstants.PrivacyRingMaxItemCount)
+ {
+ result = CreateCoinSegments(pockets);
+ }
+ else
+ {
+ result = CreatePocketSegments(pockets);
+ }
+
+ foreach (var item in result)
+ {
+ list.Add(item);
+ }
+
+ PreviewItems.RemoveRange(1, PreviewItems.Count - 1);
+ PreviewItems.AddRange(list);
+ }
+
+ private IEnumerable<PrivacyRingItemViewModel> CreateCoinSegments(IEnumerable<Pocket> pockets)
+ {
var total = pockets.Sum(x => Math.Abs(x.Amount.ToDecimal(NBitcoin.MoneyUnit.BTC)));
var start = 0.0m;
@@ -98,21 +130,35 @@ public partial class PrivacyRingViewModel : RoutableViewModel
var item = new PrivacyRingItemViewModel(this, coin, (double)start, (double)end);
- list.Add(item);
-
- _disposables.Add(item);
+ yield return item;
start = end;
}
}
+ }
- PreviewItems.RemoveRange(1, PreviewItems.Count - 1);
- PreviewItems.AddRange(list);
- });
+ private IEnumerable<PrivacyRingItemViewModel> CreatePocketSegments(IEnumerable<Pocket> pockets)
+ {
+ var total = pockets.Sum(x => Math.Abs(x.Amount.ToDecimal(NBitcoin.MoneyUnit.BTC)));
+ var start = 0.0m;
+
+ foreach (var pocket in pockets.OrderByDescending(x => x.Coins.First().HdPubKey.AnonymitySet))
+ {
+ var end = start + (Math.Abs(pocket.Amount.ToDecimal(NBitcoin.MoneyUnit.BTC)) / total);
+
+ var item = new PrivacyRingItemViewModel(this, pocket, (double)start, (double)end);
+
+ yield return item;
+
+ _disposables.Add(item);
+
+ start = end;
+ }
}
public void Dispose()
{
+ _itemsSourceList?.Dispose();
_disposables.Dispose();
}
}

PrivacyRing and PrivacyBar render threshold