Converge to existing design
ADD
WalletWasabi.Fluent/ViewModels/CoinControl/Core/CoinItem.cs
+using NBitcoin;
+using WalletWasabi.Blockchain.Analysis.Clustering;
+using WalletWasabi.Blockchain.TransactionOutputs;
+
+namespace WalletWasabi.Fluent.ViewModels.CoinControl.Core;
+
+internal class CoinItem : ItemBase
+{
+ public CoinItem(SmartCoin smartCoin)
+ {
+ Amount = smartCoin.Amount;
+ IsConfirmed = smartCoin.Confirmed;
+ IsBanned = smartCoin.IsBanned;
+ ConfirmationStatus = "";
+ IsCoinjoining = smartCoin.CoinJoinInProgress;
+ ConfirmationStatus = smartCoin.Confirmed ? "Confirmed" : "Pending confirmation";
+ BannedUntilUtcToolTip = smartCoin.BannedUntilUtc.HasValue ? $"Can't participate in coinjoin until: {smartCoin.BannedUntilUtc:g}" : "";
+ AnonymityScore = (int) smartCoin.HdPubKey.AnonymitySet;
+ Labels = smartCoin.HdPubKey.Label;
+ BannedUntilUtc = smartCoin.BannedUntilUtc;
+ }
+
+ public override DateTimeOffset? BannedUntilUtc { get; }
+ public override bool IsConfirmed { get; }
+ public override bool IsCoinjoining { get; }
+ public override bool IsBanned { get; }
+ public override string ConfirmationStatus { get; }
+ public override Money Amount { get; }
+ public override string BannedUntilUtcToolTip { get; }
+ public override int? AnonymityScore { get; }
+ public override SmartLabel Labels { get; }
+}

ADD
WalletWasabi.Fluent/ViewModels/CoinControl/Core/ItemBase.cs
+using System.Collections.Generic;
+using NBitcoin;
+using WalletWasabi.Blockchain.Analysis.Clustering;
+using WalletWasabi.Fluent.Helpers;
+
+namespace WalletWasabi.Fluent.ViewModels.CoinControl.Core;
+
+public abstract class ItemBase
+{
+ protected ItemBase() : this(new List<ItemBase>()) { }
+
+ protected ItemBase(IReadOnlyCollection<ItemBase> children)
+ {
+ Children = children;
+ }
+
+ public bool IsPrivate => Labels == CoinPocketHelper.PrivateFundsText;
+
+ public bool IsSemiPrivate => Labels == CoinPocketHelper.SemiPrivateFundsText;
+
+ public bool IsNonPrivate => !IsSemiPrivate && !IsPrivate;
+
+ public IReadOnlyCollection<ItemBase> Children { get; }
+
+ public abstract bool IsConfirmed { get; }
+
+ public abstract bool IsCoinjoining { get; }
+
+ public abstract bool IsBanned { get; }
+
+ public abstract string ConfirmationStatus { get; }
+
+ public abstract Money Amount { get; }
+
+ public abstract string BannedUntilUtcToolTip { get; }
+
+ public abstract int? AnonymityScore { get; }
+
+ public bool IsExpanded { get; set; } = true;
+
+ public abstract SmartLabel Labels { get; }
+
+ public abstract DateTimeOffset? BannedUntilUtc { get; }
+}

ADD
WalletWasabi.Fluent/ViewModels/CoinControl/Core/PocketItem.cs
+using System.Linq;
+using NBitcoin;
+using WalletWasabi.Blockchain.Analysis.Clustering;
+using WalletWasabi.Fluent.Models;
+
+namespace WalletWasabi.Fluent.ViewModels.CoinControl.Core;
+
+internal class PocketItem : ItemBase
+{
+ public PocketItem(Pocket pocket) : base(pocket.Coins.OrderByDescending(x => x.Amount).Select(coin => new CoinItem(coin)).ToList())
+ {
+ Amount = pocket.Amount;
+ IsConfirmed = pocket.Coins.All(x => x.Confirmed);
+ IsCoinjoining = pocket.Coins.Any(x => x.CoinJoinInProgress);
+ AnonymityScore = null;
+ Labels = pocket.Labels;
+ }
+
+ public override bool IsConfirmed { get; }
+ public override bool IsCoinjoining { get; }
+ public override bool IsBanned => false;
+ public override string ConfirmationStatus => "";
+ public override Money Amount { get; }
+ public override string BannedUntilUtcToolTip => "";
+ public override int? AnonymityScore { get; }
+ public override SmartLabel Labels { get; }
+ public override DateTimeOffset? BannedUntilUtc => null;
+}

ADD
WalletWasabi.Fluent/ViewModels/CoinControl/Core/Sorting.cs
+using System.Collections.Generic;
+
+namespace WalletWasabi.Fluent.ViewModels.CoinControl.Core;
+
+public static class Sorting
+{
+ public static Comparison<TSource?> SortAscending<TSource, TProperty>(Func<TSource, TProperty> selector)
+ {
+ return (x, y) => Comparer<TProperty>.Default.Compare(selector(x!), selector(y!));
+ }
+
+ public static Comparison<TSource?> SortDescending<TSource, TProperty>(Func<TSource, TProperty?> selector)
+ {
+ return (x, y) => Comparer<TProperty>.Default.Compare(selector(y!), selector(x!));
+ }
+}

MODIFY
WalletWasabi.Fluent/ViewModels/CoinControl/SelectCoinsDialogViewModel.cs
WalletWasabi.Fluent/ViewModels/CoinControl/SelectCoinsDialogViewModel.cs
@@ -3,17 +3,15 @@ using System.ComponentModel;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Models.TreeDataGrid;
-using Avalonia.Layout;
-using NBitcoin;
+using Avalonia.Controls.Templates;
using WalletWasabi.Blockchain.TransactionOutputs;
using WalletWasabi.Fluent.Helpers;
using WalletWasabi.Fluent.Models;
using WalletWasabi.Fluent.ViewModels.CoinControl.Core;
-using WalletWasabi.Fluent.ViewModels.CoinControl.Core.Cells;
using WalletWasabi.Fluent.ViewModels.CoinControl.Core.Headers;
using WalletWasabi.Fluent.ViewModels.Dialogs.Base;
using WalletWasabi.Fluent.ViewModels.Wallets;
-using ICoin = WalletWasabi.Fluent.ViewModels.CoinControl.Core.ICoin;
+using WalletWasabi.Fluent.Views.CoinControl.Core.Cells;
namespace WalletWasabi.Fluent.ViewModels.CoinControl;
@@ -29,10 +27,9 @@ public partial class SelectCoinsDialogViewModel : DialogViewModelBase<IEnumerabl
public SelectCoinsDialogViewModel(WalletViewModel walletViewModel)
{
var pockets = walletViewModel.Wallet.GetPockets();
+ var items = CreateItems(pockets);
- var nodes = ToTreeNodes(pockets);
-
- Source = new HierarchicalTreeDataGridSource<TreeNode>(nodes)
+ Source = new HierarchicalTreeDataGridSource<ItemBase>(items)
{
Columns =
{
@@ -51,72 +48,67 @@ public partial class SelectCoinsDialogViewModel : DialogViewModelBase<IEnumerabl
EnableBack = true;
}
- public HierarchicalTreeDataGridSource<TreeNode> Source { get; }
+ public HierarchicalTreeDataGridSource<ItemBase> Source { get; }
- private static HierarchicalExpanderColumn<TreeNode> ChildrenColumn(IColumn<TreeNode>? inner = null)
+ private static IColumn<ItemBase> ChildrenColumn(IColumn<ItemBase>? inner = null)
{
- inner ??= new TextColumn<TreeNode, string>("", node => "");
+ inner ??= new TextColumn<ItemBase, string>("", node => "");
- return new HierarchicalExpanderColumn<TreeNode>(
+ return new HierarchicalExpanderColumn<ItemBase>(
inner,
group => group.Children,
- node => node.Children.Count() > 1,
+ node => node.Children.Count > 1,
node => node.IsExpanded);
}
- private static TemplateColumn<TreeNode> AmountColumn()
+ private static IColumn<ItemBase> AmountColumn()
{
- return new TemplateColumn<TreeNode>(
+ return new TextColumn<ItemBase, string>(
"Amount",
- new ConstantTemplate<TreeNode>(group => ((ICoin) group.Value).Amount),
- GridLength.Auto,
- new ColumnOptions<TreeNode>
- {
- CompareAscending = TreeNodeSorting.SortAscending<ICoin, Money>(model => model.Amount),
- CompareDescending = TreeNodeSorting.SortDescending<ICoin, Money>(model => model.Amount)
- });
+ node => node.Amount.ToFormattedString(),
+ GridLength.Auto);
}
- private static TemplateColumn<TreeNode> IndicatorsColumn()
+ private static IColumn<ItemBase> IndicatorsColumn()
{
- return new TemplateColumn<TreeNode>(
+ return new TemplateColumn<ItemBase>(
"",
- new ConstantTemplate<TreeNode>(group => new IndicatorsCellViewModel((ICoin) group.Value)),
+ new FuncDataTemplate<ItemBase>((_, _) => new IndicatorsCellView(), true),
GridLength.Auto,
- new ColumnOptions<TreeNode>
+ new ColumnOptions<ItemBase>
{
- CompareAscending = TreeNodeSorting.SortAscending<ICoin, int>(GetIndicatorPriority),
- CompareDescending = TreeNodeSorting.SortDescending<ICoin, int>(GetIndicatorPriority)
+ CompareAscending = Sorting.SortAscending<ItemBase, int>(GetIndicatorPriority),
+ CompareDescending = Sorting.SortDescending<ItemBase, int>(GetIndicatorPriority)
});
}
- private static TemplateColumn<TreeNode> PrivacyScore()
+ private static IColumn<ItemBase> PrivacyScore()
{
- return new TemplateColumn<TreeNode>(
+ return new TextColumn<ItemBase, int?>(
new AnonymityScoreHeaderViewModel(),
- new ConstantTemplate<TreeNode>(group => ((ICoin) group.Value).AnonymityScore),
+ node => node.AnonymityScore,
GridLength.Auto,
- new ColumnOptions<TreeNode>
+ new TextColumnOptions<ItemBase>
{
- CompareAscending = TreeNodeSorting.SortAscending<ICoin, Money>(model => model.Amount),
- CompareDescending = TreeNodeSorting.SortDescending<ICoin, Money>(model => model.Amount)
+ CompareAscending = Sorting.SortAscending<ItemBase, int?>(b => b.AnonymityScore),
+ CompareDescending = Sorting.SortDescending<ItemBase, int?>(b => b.AnonymityScore)
});
}
- private static TemplateColumn<TreeNode> PocketColumn()
+ private static IColumn<ItemBase> PocketColumn()
{
- return new TemplateColumn<TreeNode>(
+ return new TemplateColumn<ItemBase>(
"Pocket",
- new ConstantTemplate<TreeNode>(group => new LabelsCellViewModel((ICoin) group.Value), HorizontalAlignment.Left),
+ new FuncDataTemplate<ItemBase>((_, _) => new LabelsCellView(), true),
GridLength.Star,
- new ColumnOptions<TreeNode>
+ new ColumnOptions<ItemBase>
{
- CompareAscending = TreeNodeSorting.SortAscending<ICoin, int>(GetLabelPriority),
- CompareDescending = TreeNodeSorting.SortDescending<ICoin, int>(GetLabelPriority)
+ CompareAscending = Sorting.SortAscending<ItemBase, int>(GetLabelPriority),
+ CompareDescending = Sorting.SortDescending<ItemBase, int>(GetLabelPriority)
});
}
- private static int GetLabelPriority(ICoin coin)
+ private static int GetLabelPriority(ItemBase coin)
{
if (coin.Labels == CoinPocketHelper.PrivateFundsText)
{
@@ -131,14 +123,14 @@ public partial class SelectCoinsDialogViewModel : DialogViewModelBase<IEnumerabl
return 1;
}
- private static int GetIndicatorPriority(ICoin x)
+ private static int GetIndicatorPriority(ItemBase x)
{
if (x.IsCoinjoining)
{
return 1;
}
- if (x.BannedUntil.HasValue)
+ if (x.BannedUntilUtc.HasValue)
{
return 2;
}
@@ -151,14 +143,10 @@ public partial class SelectCoinsDialogViewModel : DialogViewModelBase<IEnumerabl
return 0;
}
- private static IEnumerable<TreeNode> ToTreeNodes(IEnumerable<Pocket> pockets)
+ private static IReadOnlyCollection<ItemBase> CreateItems(IEnumerable<Pocket> pockets)
{
- return pockets.Select(
- pocket => new TreeNode(
- new PocketCoinAdapter(pocket),
- pocket.Coins
- .OrderByDescending(x => x.Amount)
- .Select(coin => new TreeNode(new SmartCoinAdapter(coin))).ToList()))
+ return pockets
+ .Select(pocket => new PocketItem(pocket))
.ToList();
}
}

MODIFY
WalletWasabi.Fluent/Views/CoinControl/Core/Cells/IndicatorsCellView.axaml
WalletWasabi.Fluent/Views/CoinControl/Core/Cells/IndicatorsCellView.axaml
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:cells="clr-namespace:WalletWasabi.Fluent.ViewModels.CoinControl.Core.Cells"
+ xmlns:core="clr-namespace:WalletWasabi.Fluent.ViewModels.CoinControl.Core"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
- x:DataType="cells:IndicatorsCellViewModel"
+ x:DataType="core:ItemBase"
x:CompileBindings="True"
x:Class="WalletWasabi.Fluent.Views.CoinControl.Core.Cells.IndicatorsCellView">

MODIFY
WalletWasabi.Fluent/Views/CoinControl/Core/Cells/LabelsCellView.axaml
WalletWasabi.Fluent/Views/CoinControl/Core/Cells/LabelsCellView.axaml
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:WalletWasabi.Fluent.Controls"
- xmlns:cells="clr-namespace:WalletWasabi.Fluent.ViewModels.CoinControl.Core.Cells"
+ xmlns:core="clr-namespace:WalletWasabi.Fluent.ViewModels.CoinControl.Core"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="WalletWasabi.Fluent.Views.CoinControl.Core.Cells.LabelsCellView"
- x:DataType="cells:LabelsCellViewModel"
+ x:DataType="core:ItemBase"
x:CompileBindings="True">
<UserControl.Resources>

MODIFY
WalletWasabi.Fluent/Views/CoinControl/SelectCoinsDialogView.axaml
WalletWasabi.Fluent/Views/CoinControl/SelectCoinsDialogView.axaml
xmlns:generic="clr-namespace:System.Collections.Generic;assembly=System.Collections"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="650"
xmlns:coinControl="clr-namespace:WalletWasabi.Fluent.ViewModels.CoinControl"
+ xmlns:treeDataGrid="clr-namespace:WalletWasabi.Fluent.TreeDataGrid"
x:DataType="coinControl:SelectCoinsDialogViewModel"
x:Class="WalletWasabi.Fluent.Views.CoinControl.SelectCoinsDialogView"
x:CompileBindings="True">
<Setter Property="Margin" Value="5 0 10 0" />
</Style>
+ <Style Selector="TreeDataGridTextCell TextBlock">
+ <Setter Property="TextAlignment" Value="Center" />
+ </Style>
+
+ <Style Selector="treeDataGrid|TreeDataGridPrivacyTextCell">
+ <Setter Property="FontFamily" Value="{StaticResource MonospacedFont}" />
+ <Setter Property="FontSize" Value="15" />
+ <Setter Property="Margin" Value="10 0" />
+ </Style>
+
<Style Selector="ContentPresenter > TextBlock">
<Setter Property="FontFamily" Value="{StaticResource MonospacedFont}" />
</Style>

Converge to existing design