Correct disposal
MODIFY
WalletWasabi.Fluent/ViewModels/CoinControl/Core/CoinControlItemViewModelBase.cs
WalletWasabi.Fluent/ViewModels/CoinControl/Core/CoinControlItemViewModelBase.cs
using System.Collections.Generic;
using System.Linq;
+using System.Reactive.Disposables;
using NBitcoin;
using ReactiveUI;
using WalletWasabi.Blockchain.Analysis.Clustering;
@@ -7,15 +8,17 @@ using WalletWasabi.Fluent.Helpers;
namespace WalletWasabi.Fluent.ViewModels.CoinControl.Core;
-public abstract class CoinControlItemViewModelBase : ViewModelBase, IHierarchicallySelectable
+public abstract class CoinControlItemViewModelBase : ViewModelBase, IHierarchicallySelectable, IDisposable
{
private bool? _isSelected;
+ private readonly CompositeDisposable _disposables = new();
public CoinControlItemViewModelBase(IEnumerable<IHierarchicallySelectable> children)
{
Selectables = children.ToList();
Children = Selectables.Cast<CoinControlItemViewModelBase>().ToList();
- HierarchicalSelection = new HierarchicalSelection(this);
+ HierarchicalSelectionHandler = new HierarchicalSelectionHandler(this);
+ HierarchicalSelectionHandler.DisposeWith(_disposables);
}
public bool IsPrivate => Labels == CoinPocketHelper.PrivateFundsText;
@@ -46,7 +49,7 @@ public abstract class CoinControlItemViewModelBase : ViewModelBase, IHierarchica
public bool IsExpanded { get; set; } = true;
- public HierarchicalSelection HierarchicalSelection { get; }
+ public HierarchicalSelectionHandler HierarchicalSelectionHandler { get; }
public bool CanBeSelected { get; protected set; }
@@ -65,4 +68,13 @@ public abstract class CoinControlItemViewModelBase : ViewModelBase, IHierarchica
this.RaiseAndSetIfChanged(ref _isSelected, value);
}
}
+
+ public void Dispose()
+ {
+ _disposables.Dispose();
+ foreach (var child in Children)
+ {
+ child.Dispose();
+ }
+ }
}

DELETE
WalletWasabi.Fluent/ViewModels/CoinControl/Core/HierarchicalSelection.cs
-using System.Collections.Generic;
-using System.Linq;
-using System.Reactive;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
-using DynamicData;
-using ReactiveUI;
-
-namespace WalletWasabi.Fluent.ViewModels.CoinControl.Core;
-
-public class HierarchicalSelection : ReactiveObject, IDisposable
-{
- private bool _canUpdate = true;
- private readonly CompositeDisposable _disposables = new();
-
- public HierarchicalSelection(IHierarchicallySelectable model)
- {
- var childrenSelectionStates = model.Selectables
- .AsObservableChangeSet()
- .AutoRefresh(x => x.IsSelected, TimeSpan.FromMilliseconds(50), scheduler: RxApp.MainThreadScheduler)
- .ToCollection()
- .Select(collection => GetSelectionState(collection.Select(selectable => selectable.IsSelected).ToList()))
- .Where(_ => _canUpdate);
-
- childrenSelectionStates.BindTo(model, x => x.IsSelected)
- .DisposeWith(_disposables);
-
- ToggleSelection = ReactiveCommand
- .Create(() => UpdateCheckedStateCascade(model, model.IsSelected ?? false))
- .DisposeWith(_disposables);
- }
-
- private void UpdateCheckedStateCascade(IHierarchicallySelectable model, bool isChecked)
- {
- _canUpdate = false;
-
- model.IsSelected = isChecked;
-
- foreach (var child in model.Selectables)
- {
- UpdateCheckedStateCascade(child, isChecked);
- }
-
- _canUpdate = true;
- }
-
- public ReactiveCommand<Unit, Unit> ToggleSelection { get; }
-
- private static bool? GetSelectionState(IList<bool?> readOnlyCollection)
- {
- bool? selectionState = readOnlyCollection.All(x => x.HasValue && x.Value)
- ? true
- : readOnlyCollection.Any(x => x.HasValue && x.Value || !x.HasValue)
- ? null
- : false;
- return selectionState;
- }
-
- public void Dispose()
- {
- _disposables.Dispose();
- }
-}

ADD
WalletWasabi.Fluent/ViewModels/CoinControl/Core/HierarchicalSelectionHandler.cs
+using System.Collections.Generic;
+using System.Linq;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using DynamicData;
+using ReactiveUI;
+
+namespace WalletWasabi.Fluent.ViewModels.CoinControl.Core;
+
+public class HierarchicalSelectionHandler : ReactiveObject, IDisposable
+{
+ private readonly CompositeDisposable _disposables = new();
+
+ private bool _canUpdate = true;
+
+ public HierarchicalSelectionHandler(IHierarchicallySelectable model)
+ {
+ Model = model;
+
+ model.Selectables
+ .AsObservableChangeSet()
+ .AutoRefresh(x => x.IsSelected)
+ .Where(_ => _canUpdate)
+ .ToCollection()
+ .Select(GetSelectionState)
+ .BindTo(model, x => x.IsSelected)
+ .DisposeWith(_disposables);
+
+ this.WhenAnyValue(x => x.Model.IsSelected)
+ .WhereNotNull()
+ .Do(isSelected => Toggle(isSelected!.Value))
+ .Subscribe();
+ }
+
+ private void Toggle(bool isSelected)
+ {
+ _canUpdate = false;
+
+ foreach (var item in Model.Selectables)
+ {
+ item.IsSelected = isSelected;
+ }
+
+ _canUpdate = true;
+ }
+
+ private IHierarchicallySelectable Model { get; }
+
+ private static bool? GetSelectionState(IReadOnlyCollection<IHierarchicallySelectable> children)
+ {
+ var selectionCount = children.Count(x => x.IsSelected == true || x.IsSelected == null);
+
+ bool? selectionState = selectionCount == 0
+ ? false
+ : children.Count == selectionCount
+ ? true
+ : null;
+ return selectionState;
+ }
+
+ public void Dispose()
+ {
+ _disposables.Dispose();
+ }
+}

MODIFY
WalletWasabi.Fluent/ViewModels/CoinControl/SelectCoinsDialogViewModel.cs
WalletWasabi.Fluent/ViewModels/CoinControl/SelectCoinsDialogViewModel.cs
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
-using System.Reactive.Linq;
+using System.Reactive.Disposables;
using Avalonia.Controls;
using Avalonia.Controls.Models.TreeDataGrid;
-using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
-using Avalonia.Data;
-using DynamicData;
using NBitcoin;
using ReactiveUI;
using WalletWasabi.Blockchain.TransactionOutputs;
@@ -31,10 +28,14 @@ namespace WalletWasabi.Fluent.ViewModels.CoinControl;
NavigationTarget = NavigationTarget.DialogScreen)]
public partial class SelectCoinsDialogViewModel : DialogViewModelBase<IEnumerable<SmartCoin>>
{
+ private readonly CompositeDisposable _disposables = new ();
+
public SelectCoinsDialogViewModel(WalletViewModel walletViewModel, IEnumerable<SmartCoin> selectedCoins)
{
var pockets = walletViewModel.Wallet.GetPockets();
var items = CreateItems(pockets);
+ items.ToList().ForEach(x => x.DisposeWith(_disposables));
+
SetSelectionState(items, selectedCoins);
var pocketColumn = PocketColumn();
@@ -69,6 +70,13 @@ public partial class SelectCoinsDialogViewModel : DialogViewModelBase<IEnumerabl
public HierarchicalTreeDataGridSource<CoinControlItemViewModelBase> Source { get; }
+ protected override void OnNavigatedFrom(bool isInHistory)
+ {
+ _disposables.Dispose();
+
+ base.OnNavigatedFrom(isInHistory);
+ }
+
private static IColumn<CoinControlItemViewModelBase> ChildrenColumn()
{
return new HierarchicalExpanderColumn<CoinControlItemViewModelBase>(

MODIFY
WalletWasabi.Fluent/Views/CoinControl/Core/Cells/SelectionCellView.axaml
WalletWasabi.Fluent/Views/CoinControl/Core/Cells/SelectionCellView.axaml
x:Class="WalletWasabi.Fluent.Views.CoinControl.Core.Cells.SelectionCellView"
x:DataType="core:CoinControlItemViewModelBase"
x:CompileBindings="True">
- <CheckBox IsChecked="{Binding IsSelected}" IsEnabled="{Binding CanBeSelected}" Command="{Binding HierarchicalSelection.ToggleSelection}" />
+ <CheckBox IsChecked="{Binding IsSelected}" IsEnabled="{Binding CanBeSelected}" />
</UserControl>

Correct disposal