privacy ring - partial commit (wip)
MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Home/Tiles/PrivacyControlTileViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Home/Tiles/PrivacyControlTileViewModel.cs
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
+using System.Threading.Tasks;
+using System.Windows.Input;
using NBitcoin;
+using ReactiveUI;
using WalletWasabi.Fluent.Helpers;
+using WalletWasabi.Fluent.ViewModels.Navigation;
using WalletWasabi.Wallets;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles;
public partial class PrivacyControlTileViewModel : TileViewModel
{
+ private readonly WalletViewModel _walletVm;
private readonly IObservable<Unit> _balanceChanged;
private readonly Wallet _wallet;
[AutoNotify] private bool _fullyMixed;
@@ -19,9 +24,14 @@ public partial class PrivacyControlTileViewModel : TileViewModel
public PrivacyControlTileViewModel(WalletViewModel walletVm, IObservable<Unit> balanceChanged)
{
_wallet = walletVm.Wallet;
+ _walletVm = walletVm;
_balanceChanged = balanceChanged;
+
+ ShowDetailsCommand = ReactiveCommand.Create(ShowDetails);
}
+ public ICommand ShowDetailsCommand { get; }
+
protected override void OnActivated(CompositeDisposable disposables)
{
base.OnActivated(disposables);
@@ -31,6 +41,11 @@ public partial class PrivacyControlTileViewModel : TileViewModel
.DisposeWith(disposables);
}
+ private void ShowDetails()
+ {
+ NavigationState.Instance.DialogScreenNavigation.To(new PrivacyRingViewModel(_walletVm, _balanceChanged));
+ }
+
private void Update()
{
var privateThreshold = _wallet.KeyManager.AnonScoreTarget;

ADD
WalletWasabi.Fluent/ViewModels/Wallets/Home/Tiles/PrivacyRingViewModel.cs
+using Avalonia;
+using DynamicData;
+using DynamicData.Binding;
+using ReactiveUI;
+using System.Linq;
+using System.Reactive;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using WalletWasabi.Blockchain.TransactionOutputs;
+using WalletWasabi.Fluent.Extensions;
+using WalletWasabi.Fluent.ViewModels.Navigation;
+using WalletWasabi.Fluent.ViewModels.Wallets.Advanced.WalletCoins;
+using WalletWasabi.Wallets;
+
+namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles;
+
+public class PrivacyRingViewModel : RoutableViewModel
+{
+ private IObservable<Unit> _coinsUpdated;
+
+ public PrivacyRingViewModel(WalletViewModel walletViewModel, IObservable<Unit> balanceChanged)
+ {
+ Title = "Wallet Coins";
+ NextCommand = CancelCommand;
+
+ _coinsUpdated =
+ balanceChanged.ToSignal()
+ .Merge(walletViewModel
+ .WhenAnyValue(w => w.IsCoinJoining)
+ .ToSignal());
+
+ _coinsUpdated
+ .Select(_ => walletViewModel.Wallet.Coins)
+ .Subscribe(RefreshCoinsList);
+ }
+
+ public override sealed string Title { get; protected set; }
+
+ public ObservableCollectionExtended<PrivacyRingItemViewModel> Items { get; } = new();
+
+ private void RefreshCoinsList(ICoinsView items)
+ {
+ var total = items.Sum(x => Math.Abs(x.Amount.ToDecimal(NBitcoin.MoneyUnit.BTC)));
+
+ var start = 0.0m;
+
+ for (int i = 0; i < items.Count(); i++)
+ {
+ var coin = items.ElementAt(i);
+ var end = start + (Math.Abs(coin.Amount.ToDecimal(NBitcoin.MoneyUnit.BTC)) / total);
+
+ var item = new PrivacyRingItemViewModel(coin, (double)start, (double)end);
+
+ Items.Add(item);
+
+ start = end;
+ }
+ }
+
+ public void Dispose()
+ {
+ foreach (var item in Items)
+ {
+ item.Dispose();
+ }
+ }
+}
+
+public class PrivacyRingItemViewModel : WalletCoinViewModel
+{
+ private const double TotalAngle = 2d * Math.PI;
+ private const double UprightAngle = Math.PI / 2d;
+
+ public PrivacyRingItemViewModel(SmartCoin coin, double start, double end) : base(coin)
+ {
+ var outerRadius = 200;
+ var innerRadius = 150d;
+ Arc1Size = new Size(outerRadius, outerRadius);
+ Arc2Size = new Size(innerRadius, innerRadius);
+
+ var margin = 2d;
+
+ var startAngle = (TotalAngle * start) - UprightAngle;
+ var endAngle = (TotalAngle * end) - UprightAngle;
+
+ var outerOffset = outerRadius == 0 ? 0 : (margin / (TotalAngle * outerRadius) * TotalAngle);
+ var innerOffset = innerRadius == 0 ? 0 : (margin / (TotalAngle * innerRadius) * TotalAngle);
+
+ Origin1 = GetAnglePoint(outerRadius, startAngle + outerOffset);
+ Arc1 = GetAnglePoint(outerRadius, endAngle - outerOffset);
+ Origin2 = GetAnglePoint(innerRadius, endAngle - innerOffset);
+ Arc2 = GetAnglePoint(innerRadius, startAngle + innerOffset);
+ }
+
+ public Point Origin1 { get; }
+ public Point Arc1 { get; }
+ public Size Arc1Size { get; }
+
+ public Point Origin2 { get; }
+ public Point Arc2 { get; }
+ public Size Arc2Size { get; }
+
+ private Point GetAnglePoint(double r, double angle)
+ {
+ var x = r * Math.Cos(angle);
+ var y = r * Math.Sin(angle);
+ return new Point(x, y);
+ }
+}

MODIFY
WalletWasabi.Fluent/Views/Wallets/Home/Tiles/PrivacyControlTileView.axaml
WalletWasabi.Fluent/Views/Wallets/Home/Tiles/PrivacyControlTileView.axaml
</StackPanel>
<StackPanel VerticalAlignment="Center" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0 3">
- <Image Height="36" Width="36" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 0 10 0"
- Source="{StaticResource privacy_indicator_good}" />
+
+ <Button Command="{Binding ShowDetailsCommand}" Classes="plain"
+ ToolTip.Tip="Show Details" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 0 10 0"
+ Height="36" Width="36">
+ <Image Source="{StaticResource privacy_indicator_good}" />
+ </Button>
+
<TextBlock
VerticalAlignment="Center" HorizontalAlignment="Center"
TextAlignment="Center"

ADD
WalletWasabi.Fluent/Views/Wallets/Home/Tiles/PrivacyRingView.axaml
+<UserControl xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:c="using:WalletWasabi.Fluent.Controls"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ x:Class="WalletWasabi.Fluent.Views.Wallets.Home.Tiles.PrivacyRingView">
+ <UserControl.Styles>
+ <Style Selector="Path:pointerover">
+ <Setter Property="RenderTransformOrigin" Value="0.5,0.5" />
+ <Setter Property="RenderTransform">
+ <ScaleTransform ScaleX="1.05" ScaleY="1.05" />
+ </Setter>
+ <Setter Property="Stroke" Value="Black" />
+ <Setter Property="StrokeThickness" Value="1" />
+ </Style>
+ </UserControl.Styles>
+ <c:ContentArea Title="{Binding Title}"
+ EnableNext="True" NextContent="Done"
+ EnableCancel="False"
+ EnableBack="True">
+ <ItemsControl Items="{Binding Items}" ClipToBounds="False" Margin="300 300 0 0">
+ <ItemsControl.ItemsPanel>
+ <ItemsPanelTemplate>
+ <Panel />
+ </ItemsPanelTemplate>
+ </ItemsControl.ItemsPanel>
+ <ItemsControl.ItemTemplate>
+ <DataTemplate>
+ <Panel>
+ <Path Stretch="None" StrokeThickness="1" Fill="Red" Height="200" Width="200">
+ <Path.Data>
+ <PathGeometry>
+ <PathFigure StartPoint="{Binding Origin1}" IsClosed="True">
+ <ArcSegment Size="{Binding Arc1Size}" Point="{Binding Arc1}" />
+ <LineSegment Point="{Binding Origin2}" />
+ <ArcSegment Size="{Binding Arc2Size}" Point="{Binding Arc2}" SweepDirection="CounterClockwise" />
+ </PathFigure>
+ </PathGeometry>
+ </Path.Data>
+ </Path>
+ </Panel>
+ </DataTemplate>
+ </ItemsControl.ItemTemplate>
+ </ItemsControl>
+ </c:ContentArea>
+</UserControl>

ADD
WalletWasabi.Fluent/Views/Wallets/Home/Tiles/PrivacyRingView.axaml.cs
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace WalletWasabi.Fluent.Views.Wallets.Home.Tiles;
+public partial class PrivacyRingView : UserControl
+{
+ public PrivacyRingView()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+}

privacy ring - partial commit (wip)