Synchronize selection
MODIFY
WalletWasabi.Fluent/ViewModels/CoinControl/CoinSelectorViewModel.cs
WalletWasabi.Fluent/ViewModels/CoinControl/CoinSelectorViewModel.cs
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Collections.ObjectModel;
using System.Linq;
+using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Models.TreeDataGrid;
@@ -9,30 +11,39 @@ using DynamicData;
using NBitcoin;
using ReactiveUI;
using WalletWasabi.Blockchain.TransactionOutputs;
-using WalletWasabi.Fluent.Extensions;
using WalletWasabi.Fluent.Helpers;
-using WalletWasabi.Fluent.Models;
using WalletWasabi.Fluent.TreeDataGrid;
using WalletWasabi.Fluent.ViewModels.CoinControl.Core;
using WalletWasabi.Fluent.ViewModels.Wallets;
using WalletWasabi.Fluent.Views.CoinControl.Core.Cells;
using WalletWasabi.Fluent.Views.CoinControl.Core.Headers;
+using WalletWasabi.Wallets;
namespace WalletWasabi.Fluent.ViewModels.CoinControl;
public partial class CoinSelectorViewModel : ViewModelBase, IDisposable
{
+ private readonly CompositeDisposable _disposables = new();
+ private readonly ObservableCollection<CoinControlItemViewModelBase> _source;
+ private readonly Wallet _wallet;
[AutoNotify] private IReadOnlyCollection<SmartCoin> _selectedCoins = ImmutableList<SmartCoin>.Empty;
+ private IDisposable _selectedCoinsUpdater = Disposable.Empty;
- public CoinSelectorViewModel(WalletViewModel walletViewModel, IEnumerable<SmartCoin> initialCoinSelection)
+ public CoinSelectorViewModel(WalletViewModel walletViewModel, IList<SmartCoin> initialCoinSelection)
{
- var pockets = walletViewModel.Wallet.GetPockets();
- var pocketItems = CreatePocketItems(pockets);
+ _wallet = walletViewModel.Wallet;
+ _source = new ObservableCollection<CoinControlItemViewModelBase>();
- SyncSelectedItems(pocketItems, initialCoinSelection);
- CollapseUnselectedPockets(pocketItems);
+ Refresh(_source, initialCoinSelection);
+ CollapseUnselectedPockets();
- TreeDataGridSource = new HierarchicalTreeDataGridSource<CoinControlItemViewModelBase>(pocketItems)
+ walletViewModel.UiTriggers.TransactionsUpdateTrigger
+ .Do(_ => Refresh())
+ .Do(_ => RenewSelectionUpdater())
+ .Subscribe()
+ .DisposeWith(_disposables);
+
+ TreeDataGridSource = new HierarchicalTreeDataGridSource<CoinControlItemViewModelBase>(_source)
{
Columns =
{
@@ -40,57 +51,108 @@ public partial class CoinSelectorViewModel : ViewModelBase, IDisposable
IndicatorsColumn(),
AmountColumn(),
AnonymityScoreColumn(),
- PocketColumn(),
+ PocketColumn()
}
};
- var coins = GetAllCoinItems(pocketItems);
-
- coins
- .AsObservableChangeSet(x => x.SmartCoin)
- .AutoRefresh(x => x.IsSelected, TimeSpan.FromMilliseconds(100), scheduler: RxApp.MainThreadScheduler)
- .ToCollection()
- .Select(x => x.Where(m => m.IsSelected == true))
- .Select(models => models.Select(x => x.SmartCoin).ToImmutableList())
- .BindTo(this, model => model.SelectedCoins);
+ TreeDataGridSource.DisposeWith(_disposables);
}
public HierarchicalTreeDataGridSource<CoinControlItemViewModelBase> TreeDataGridSource { get; }
public void Dispose()
{
- foreach (var pocket in TreeDataGridSource.Items.OfType<PocketCoinControlItemViewModel>())
+ _disposables.Dispose();
+ _selectedCoinsUpdater.Dispose();
+ Dispose(_source);
+ }
+
+ private static void Populate(IList<CoinControlItemViewModelBase> pocketItems, Wallet wallet)
+ {
+ var newItems = wallet
+ .GetPockets()
+ .Select(pocket => new PocketCoinControlItemViewModel(pocket));
+
+ Dispose(pocketItems);
+
+ pocketItems.Clear();
+ pocketItems.AddRange(newItems);
+ }
+
+ private static void Dispose(IEnumerable<CoinControlItemViewModelBase> items)
+ {
+ foreach (var pocket in items.OfType<PocketCoinControlItemViewModel>())
{
pocket.Dispose();
}
+ }
+
+ private void RenewSelectionUpdater()
+ {
+ _selectedCoinsUpdater.Dispose(); // Dispose any previous updater
+
+ _selectedCoinsUpdater = GetAllCoins()
+ .AsObservableChangeSet()
+ .AutoRefresh(x => x.IsSelected)
+ .ToCollection()
+ .Select(list => new ReadOnlyCollection<SmartCoin>(list.Where(item => item.IsSelected == true).Select(x => x.SmartCoin).ToList()))
+ .BindTo(this, x => x.SelectedCoins);
+ }
- TreeDataGridSource.Dispose();
+ private void CollapseUnselectedPockets()
+ {
+ foreach (var pocket in _source.Where(x => x.IsSelected == false))
+ {
+ pocket.IsExpanded = false;
+ }
+ }
+
+ private IList<SmartCoin> GetSelectedCoins()
+ {
+ return _source
+ .SelectMany(model => model.Children)
+ .Cast<CoinCoinControlItemViewModel>()
+ .Where(x => x.IsSelected == true)
+ .Select(x => x.SmartCoin)
+ .ToList();
}
- private static IList<CoinCoinControlItemViewModel> GetAllCoinItems(IEnumerable<PocketCoinControlItemViewModel> pockets)
+ private List<CoinCoinControlItemViewModel> GetAllCoins()
{
- return pockets
- .SelectMany(x => x.Children)
- .OfType<CoinCoinControlItemViewModel>()
+ return _source
+ .SelectMany(model => model.Children)
+ .Cast<CoinCoinControlItemViewModel>()
.ToList();
}
- private static void SyncSelectedItems(IEnumerable<PocketCoinControlItemViewModel> items, IEnumerable<SmartCoin> selectedCoins)
+ private void SyncSelectedItems(IList<SmartCoin> selectedCoins)
{
- var allCoins = GetAllCoinItems(items);
- var selected = allCoins.Where(x => selectedCoins.Any(other => other == x.SmartCoin));
- foreach (var viewModel in selected)
+ var coinItems = _source.SelectMany(x => x.Children).Cast<CoinCoinControlItemViewModel>();
+ foreach (var coinItem in coinItems)
{
- viewModel.IsSelected = true;
+ coinItem.IsSelected = selectedCoins.Any(x => x == coinItem.SmartCoin);
}
}
- private static void CollapseUnselectedPockets(IReadOnlyCollection<PocketCoinControlItemViewModel> pocketItems)
+ private void Refresh()
{
- foreach (var pocket in pocketItems.Where(x => x.IsSelected == false))
+ Refresh(_source, GetSelectedCoins());
+ }
+
+ private void Refresh(IList<CoinControlItemViewModelBase> collection, IList<SmartCoin> selectedItems)
{
- pocket.IsExpanded = false;
+ Populate(collection, _wallet);
+ SyncSelectedItems(selectedItems);
}
+
+ private static Comparison<TSource?> SortAscending<TSource, TProperty>(Func<TSource, TProperty> selector)
+ {
+ return (x, y) => Comparer<TProperty>.Default.Compare(selector(x!), selector(y!));
+ }
+
+ private static Comparison<TSource?> SortDescending<TSource, TProperty>(Func<TSource, TProperty> selector)
+ {
+ return (x, y) => Comparer<TProperty>.Default.Compare(selector(y!), selector(x!));
}
private static int GetLabelPriority(CoinControlItemViewModelBase coin)
@@ -128,23 +190,6 @@ public partial class CoinSelectorViewModel : ViewModelBase, IDisposable
return 0;
}
- private static IReadOnlyCollection<PocketCoinControlItemViewModel> CreatePocketItems(IEnumerable<Pocket> pockets)
- {
- return pockets
- .Select(pocket => new PocketCoinControlItemViewModel(pocket))
- .ToList();
- }
-
- private static Comparison<TSource?> SortAscending<TSource, TProperty>(Func<TSource, TProperty> selector)
- {
- return (x, y) => Comparer<TProperty>.Default.Compare(selector(x!), selector(y!));
- }
-
- private static Comparison<TSource?> SortDescending<TSource, TProperty>(Func<TSource, TProperty> selector)
- {
- return (x, y) => Comparer<TProperty>.Default.Compare(selector(y!), selector(x!));
- }
-
private static IColumn<CoinControlItemViewModelBase> ChildrenColumn()
{
return new HierarchicalExpanderColumn<CoinControlItemViewModelBase>(

DELETE
WalletWasabi.Fluent/ViewModels/CoinControl/CoinSelectorViewModel2.cs
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Collections.ObjectModel;
-using System.Linq;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
-using Avalonia.Controls;
-using Avalonia.Controls.Models.TreeDataGrid;
-using Avalonia.Controls.Templates;
-using DynamicData;
-using NBitcoin;
-using WalletWasabi.Blockchain.TransactionOutputs;
-using WalletWasabi.Fluent.Helpers;
-using WalletWasabi.Fluent.TreeDataGrid;
-using WalletWasabi.Fluent.ViewModels.CoinControl.Core;
-using WalletWasabi.Fluent.ViewModels.Wallets;
-using WalletWasabi.Fluent.Views.CoinControl.Core.Cells;
-using WalletWasabi.Fluent.Views.CoinControl.Core.Headers;
-using WalletWasabi.Wallets;
-
-namespace WalletWasabi.Fluent.ViewModels.CoinControl;
-
-public partial class CoinSelectorViewModel2 : ViewModelBase, IDisposable
-{
- private readonly Wallet _wallet;
- private readonly CompositeDisposable _disposables = new();
- [AutoNotify] private IReadOnlyCollection<SmartCoin> _selectedCoins = ImmutableList<SmartCoin>.Empty;
- private readonly ObservableCollection<CoinControlItemViewModelBase> _source;
-
- public CoinSelectorViewModel2(WalletViewModel walletViewModel, IList<SmartCoin> initialCoinSelection)
- {
- _wallet = walletViewModel.Wallet;
- _source = new ObservableCollection<CoinControlItemViewModelBase>();
- Refresh(_source, initialCoinSelection);
- CollapseUnselectedPockets();
-
- walletViewModel.UiTriggers.TransactionsUpdateTrigger
- .Do(_ => Refresh())
- .Subscribe()
- .DisposeWith(_disposables);
-
- TreeDataGridSource = new HierarchicalTreeDataGridSource<CoinControlItemViewModelBase>(_source)
- {
- Columns =
- {
- ChildrenColumn(),
- IndicatorsColumn(),
- AmountColumn(),
- AnonymityScoreColumn(),
- PocketColumn()
- }
- };
-
- TreeDataGridSource.DisposeWith(_disposables);
- }
-
- private void CollapseUnselectedPockets()
- {
- foreach (var pocket in _source.Where(x => x.IsSelected == false))
- {
- pocket.IsExpanded = false;
- }
- }
-
- public HierarchicalTreeDataGridSource<CoinControlItemViewModelBase> TreeDataGridSource { get; }
-
- public void Dispose()
- {
- _disposables.Dispose();
- Dispose(_source);
- }
-
- private IList<SmartCoin> GetSelectedCoins()
- {
- return _source
- .SelectMany(model => model.Children)
- .Cast<CoinCoinControlItemViewModel>()
- .Where(x => x.IsSelected == true)
- .Select(x => x.SmartCoin)
- .ToList();
- }
-
- private void SyncSelectedItems(IList<SmartCoin> selectedCoins)
- {
- var coinItems = _source.SelectMany(x => x.Children).Cast<CoinCoinControlItemViewModel>();
- foreach (var coinItem in coinItems)
- {
- coinItem.IsSelected = selectedCoins.Any(x => x == coinItem.SmartCoin);
- }
- }
-
- private static int GetLabelPriority(CoinControlItemViewModelBase coin)
- {
- if (coin.Labels == CoinPocketHelper.PrivateFundsText)
- {
- return 3;
- }
-
- if (coin.Labels == CoinPocketHelper.SemiPrivateFundsText)
- {
- return 2;
- }
-
- return 1;
- }
-
- private static int GetIndicatorPriority(CoinControlItemViewModelBase x)
- {
- if (x.IsCoinjoining)
- {
- return 1;
- }
-
- if (x.BannedUntilUtc.HasValue)
- {
- return 2;
- }
-
- if (!x.IsConfirmed)
- {
- return 3;
- }
-
- return 0;
- }
-
- private static Comparison<TSource?> SortAscending<TSource, TProperty>(Func<TSource, TProperty> selector)
- {
- return (x, y) => Comparer<TProperty>.Default.Compare(selector(x!), selector(y!));
- }
-
- private static Comparison<TSource?> SortDescending<TSource, TProperty>(Func<TSource, TProperty> selector)
- {
- return (x, y) => Comparer<TProperty>.Default.Compare(selector(y!), selector(x!));
- }
-
- private static IColumn<CoinControlItemViewModelBase> ChildrenColumn()
- {
- return new HierarchicalExpanderColumn<CoinControlItemViewModelBase>(
- SelectionColumn(),
- group => group.Children,
- node => node.Children.Count > 1,
- node => node.IsExpanded);
- }
-
- private static TemplateColumn<CoinControlItemViewModelBase> SelectionColumn()
- {
- return new TemplateColumn<CoinControlItemViewModelBase>(
- "",
- new FuncDataTemplate<CoinControlItemViewModelBase>(
- (_, _) => new SelectionCellView(),
- true),
- GridLength.Auto);
- }
-
- private static IColumn<CoinControlItemViewModelBase> AmountColumn()
- {
- return new PlainTextColumn<CoinControlItemViewModelBase>(
- "Amount",
- node => node.Amount.ToFormattedString(),
- GridLength.Auto,
- new ColumnOptions<CoinControlItemViewModelBase>
- {
- CompareAscending = SortAscending<CoinControlItemViewModelBase, Money>(x => x.Amount),
- CompareDescending = SortDescending<CoinControlItemViewModelBase, Money>(x => x.Amount)
- });
- }
-
- private static IColumn<CoinControlItemViewModelBase> IndicatorsColumn()
- {
- return new TemplateColumn<CoinControlItemViewModelBase>(
- "",
- new FuncDataTemplate<CoinControlItemViewModelBase>((_, _) => new IndicatorsCellView(), true),
- GridLength.Auto,
- new ColumnOptions<CoinControlItemViewModelBase>
- {
- CompareAscending = SortAscending<CoinControlItemViewModelBase, int>(GetIndicatorPriority),
- CompareDescending = SortDescending<CoinControlItemViewModelBase, int>(GetIndicatorPriority)
- });
- }
-
- private static IColumn<CoinControlItemViewModelBase> AnonymityScoreColumn()
- {
- return new PlainTextColumn<CoinControlItemViewModelBase>(
- new AnonymityScoreHeaderView(),
- node => node.AnonymityScore.ToString(),
- GridLength.Auto,
- new TextColumnOptions<CoinControlItemViewModelBase>
- {
- CompareAscending = SortAscending<CoinControlItemViewModelBase, int?>(b => b.AnonymityScore),
- CompareDescending = SortDescending<CoinControlItemViewModelBase, int?>(b => b.AnonymityScore)
- });
- }
-
- private static IColumn<CoinControlItemViewModelBase> PocketColumn()
- {
- return new TemplateColumn<CoinControlItemViewModelBase>(
- "Pocket",
- new FuncDataTemplate<CoinControlItemViewModelBase>((_, _) => new LabelsCellView(), true),
- GridLength.Star,
- new ColumnOptions<CoinControlItemViewModelBase>
- {
- CompareAscending = SortAscending<CoinControlItemViewModelBase, int>(GetLabelPriority),
- CompareDescending = SortDescending<CoinControlItemViewModelBase, int>(GetLabelPriority)
- });
- }
-
- private void Refresh()
- {
- Refresh(_source, GetSelectedCoins());
- }
-
- private void Refresh(IList<CoinControlItemViewModelBase> collection, IList<SmartCoin> selectedItems)
- {
- Populate(collection, _wallet);
- SyncSelectedItems(selectedItems);
- }
-
- private static void Populate(IList<CoinControlItemViewModelBase> pocketItems, Wallet wallet)
- {
- var newItems = wallet
- .GetPockets()
- .Select(pocket => new PocketCoinControlItemViewModel(pocket));
-
- Dispose(pocketItems);
-
- pocketItems.Clear();
- pocketItems.AddRange(newItems);
- }
-
- private static void Dispose(IEnumerable<CoinControlItemViewModelBase> items)
- {
- foreach (var pocket in items.OfType<PocketCoinControlItemViewModel>())
- {
- pocket.Dispose();
- }
- }
-}

MODIFY
WalletWasabi.Fluent/ViewModels/CoinControl/SelectCoinsDialogViewModel.cs
WalletWasabi.Fluent/ViewModels/CoinControl/SelectCoinsDialogViewModel.cs
@@ -26,7 +26,7 @@ public partial class SelectCoinsDialogViewModel : DialogViewModelBase<IEnumerabl
_walletViewModel = walletViewModel;
_transactionInfo = transactionInfo;
- CoinSelector = new CoinSelectorViewModel2(walletViewModel, selectedCoins);
+ CoinSelector = new CoinSelectorViewModel(walletViewModel, selectedCoins);
var coinsChanged = CoinSelector.WhenAnyValue(x => x.SelectedCoins);
@@ -37,7 +37,7 @@ public partial class SelectCoinsDialogViewModel : DialogViewModelBase<IEnumerabl
SetupCancel(false, true, false);
}
- public CoinSelectorViewModel2 CoinSelector { get; }
+ public CoinSelectorViewModel CoinSelector { get; }
public IObservable<bool> EnoughSelected { get; }

Synchronize selection