Put context menu in CoinListView to DataTemplate. Put DequeueCoin function to CoinViewModel. Make sure send tab cannot dequeue.
MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/CoinJoinTabViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/CoinJoinTabViewModel.cs
@@ -21,6 +21,7 @@ using WalletWasabi.CoinJoin.Client.Rounds;
using WalletWasabi.Gui.Helpers;
using System.Security;
using WalletWasabi.CoinJoin.Client.Clients.Queuing;
+using WalletWasabi.Blockchain.TransactionOutputs;
namespace WalletWasabi.Gui.Controls.WalletExplorer
{
@@ -57,13 +58,15 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
CoinsList = new CoinListViewModel(Global, CoinListContainerType.CoinJoinTabViewModel);
- Observable.FromEventPattern(CoinsList, nameof(CoinsList.DequeueCoinsPressed)).Subscribe(_ => OnCoinsListDequeueCoinsPressedAsync());
+ Observable
+ .FromEventPattern<SmartCoin>(CoinsList, nameof(CoinsList.DequeueCoinsPressed))
+ .Subscribe(async x => await DoDequeueAsync(x.EventArgs));
AmountQueued = Money.Zero; // Global.ChaumianClient.State.SumAllQueuedCoinAmounts();
- EnqueueCommand = ReactiveCommand.CreateFromTask(async () => await DoEnqueueAsync(CoinsList.Coins.Where(c => c.IsSelected)));
+ EnqueueCommand = ReactiveCommand.CreateFromTask(async () => await DoEnqueueAsync(CoinsList.Coins.Where(c => c.IsSelected).Select(c => c.Model)));
- DequeueCommand = ReactiveCommand.CreateFromTask(async () => await DoDequeueAsync(CoinsList.Coins.Where(c => c.IsSelected)));
+ DequeueCommand = ReactiveCommand.CreateFromTask(async () => await DoDequeueAsync(CoinsList.Coins.Where(c => c.IsSelected).Select(x => x.Model)));
PrivacySomeCommand = ReactiveCommand.Create(() => TargetPrivacy = TargetPrivacy.Some);
@@ -190,12 +193,15 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
return base.OnClose();
}
- private async Task DoDequeueAsync(IEnumerable<CoinViewModel> selectedCoins)
+ private async Task DoDequeueAsync(params SmartCoin[] coins)
+ => await DoDequeueAsync(coins as IEnumerable<SmartCoin>);
+
+ private async Task DoDequeueAsync(IEnumerable<SmartCoin> coins)
{
IsDequeueBusy = true;
try
{
- if (!selectedCoins.Any())
+ if (!coins.Any())
{
NotificationHelpers.Warning("No coins are selected.", "");
return;
@@ -203,7 +209,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
try
{
- await Global.ChaumianClient.DequeueCoinsFromMixAsync(selectedCoins.Select(c => c.Model).ToArray(), DequeueReason.UserRequested);
+ await Global.ChaumianClient.DequeueCoinsFromMixAsync(coins.ToArray(), DequeueReason.UserRequested);
}
catch (Exception ex)
{
@@ -218,12 +224,12 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
}
}
- private async Task DoEnqueueAsync(IEnumerable<CoinViewModel> selectedCoins)
+ private async Task DoEnqueueAsync(IEnumerable<SmartCoin> coins)
{
IsEnqueueBusy = true;
try
{
- if (!selectedCoins.Any())
+ if (!coins.Any())
{
NotificationHelpers.Warning("No coins are selected.", "");
return;
@@ -238,7 +244,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
NotificationHelpers.Warning(PasswordHelper.CompatibilityPasswordWarnMessage);
}
- await Global.ChaumianClient.QueueCoinsToMixAsync(Password, selectedCoins.Select(c => c.Model).ToArray());
+ await Global.ChaumianClient.QueueCoinsToMixAsync(Password, coins.ToArray());
}
catch (SecurityException ex)
{
@@ -352,24 +358,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
public CoinListViewModel CoinsList { get; }
- private async void OnCoinsListDequeueCoinsPressedAsync()
- {
- try
- {
- var selectedCoin = CoinsList.SelectedCoin;
- if (selectedCoin is null)
- {
- return;
- }
-
- await DoDequeueAsync(new[] { selectedCoin });
- }
- catch (Exception ex)
- {
- Logger.LogWarning(ex);
- }
- }
-
public Money AmountQueued
{
get => _amountQueued;

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/CoinListView.xaml
WalletWasabi.Gui/Controls/WalletExplorer/CoinListView.xaml
</StackPanel>
<controls:BusyIndicator IsBusy="{Binding IsCoinListLoading}" Text="Loading...">
<controls:ExtendedListBox Items="{Binding Coins}" VirtualizationMode="None" SelectedItem="{Binding Path=SelectedCoin, Mode=TwoWay}">
- <controls:ExtendedListBox.ContextMenu>
- <ContextMenu>
+ <controls:ExtendedListBox.ItemTemplate>
+ <DataTemplate>
+ <Grid>
+ <Grid.ContextMenu>
+ <!-- If you want to add more items to the context menu and you want the Send tab to show it too, then place IsVisible="{Binding InCoinJoinContainer}" directly to the Dequeue MenuItem. -->
+ <ContextMenu IsVisible="{Binding InCoinJoinContainer}">
<MenuItem Header="Dequeue from CoinJoin" Command="{Binding DequeueCoin}">
<MenuItem.Icon>
<DrawingPresenter HorizontalAlignment="Left" Height="16" Width="16" Stretch="Fill" Drawing="{StaticResource CoinListView_DequeueCoin}" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
- </controls:ExtendedListBox.ContextMenu>
- <controls:ExtendedListBox.ItemTemplate>
- <DataTemplate>
- <Grid>
+ </Grid.ContextMenu>
<Expander Name="coinExpander" ExpandDirection="Down" Classes="coloredExpander" Background="{Binding ElementName=coinExpander, Path=IsExpanded, Converter={StaticResource CoinItemExpanderColorConverter}}">
<Grid HorizontalAlignment="Left" ColumnDefinitions="150, *" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto" MaxWidth="800" Margin="35 10 0 25">
<TextBlock Classes="monospaceFont" Text="Transaction Id:" Grid.Row="0" />

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/CoinListViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/CoinListViewModel.cs
@@ -58,14 +58,13 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
public Global Global { get; }
public CoinListContainerType CoinListContainerType { get; }
- public ReactiveCommand<Unit, Unit> DequeueCoin { get; }
public ReactiveCommand<Unit, Unit> SelectAllCheckBoxCommand { get; }
public ReactiveCommand<Unit, Unit> SelectPrivateCheckBoxCommand { get; }
public ReactiveCommand<Unit, Unit> SelectNonPrivateCheckBoxCommand { get; }
public ReactiveCommand<Unit, Unit> SortCommand { get; }
public ReactiveCommand<Unit, Unit> InitList { get; }
- public event EventHandler DequeueCoinsPressed;
+ public event EventHandler<SmartCoin> DequeueCoinsPressed;
public event EventHandler CoinListShown;
@@ -82,15 +81,9 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
public CoinViewModel SelectedCoin
{
get => _selectedCoin;
- set
- {
- this.RaiseAndSetIfChanged(ref _selectedCoin, value);
- this.RaisePropertyChanged(nameof(CanDeqeue));
- }
+ set => this.RaiseAndSetIfChanged(ref _selectedCoin, value);
}
- public bool CanDeqeue => SelectedCoin?.CoinJoinInProgress ?? false;
-
public bool? SelectAllCheckBoxState
{
get => _selectAllCheckBoxState;
@@ -109,6 +102,11 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
set => this.RaiseAndSetIfChanged(ref _statusSortDirection, value);
}
+ public void PressDequeue(SmartCoin coin)
+ {
+ DequeueCoinsPressed?.Invoke(this, coin);
+ }
+
public SortOrder AmountSortDirection
{
get => _amountSortDirection;
@@ -326,17 +324,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
}
});
- DequeueCoin = ReactiveCommand.Create(() =>
- {
- if (SelectedCoin is null)
- {
- return;
- }
-
- DequeueCoinsPressed?.Invoke(this, EventArgs.Empty);
- },
- this.WhenAnyValue(x => x.CanDeqeue));
-
SelectAllCheckBoxCommand = ReactiveCommand.Create(() =>
{
switch (SelectAllCheckBoxState)
@@ -399,7 +386,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
.Merge(SelectNonPrivateCheckBoxCommand.ThrownExceptions)
.Merge(SelectPrivateCheckBoxCommand.ThrownExceptions)
.Merge(SelectAllCheckBoxCommand.ThrownExceptions)
- .Merge(DequeueCoin.ThrownExceptions)
.Merge(SortCommand.ThrownExceptions)
.ObserveOn(RxApp.TaskpoolScheduler)
.Subscribe(ex => Logger.LogError(ex));
@@ -448,7 +434,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
RootList.RemoveMany(coinToRemove.Select(kp => kp.Value));
- var newCoinViewModels = coinToAdd.Select(c => new CoinViewModel(Global, c)).ToArray();
+ var newCoinViewModels = coinToAdd.Select(c => new CoinViewModel(this, Global, c)).ToArray();
foreach (var cvm in newCoinViewModels)
{
SubscribeToCoinEvents(cvm);

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/CoinViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/CoinViewModel.cs
@@ -25,12 +25,19 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
private ObservableAsPropertyHelper<bool> _unspent;
private ObservableAsPropertyHelper<bool> _confirmed;
private ObservableAsPropertyHelper<bool> _unavailable;
+
+ public CoinListViewModel Owner { get; }
public Global Global { get; set; }
+ public bool InCoinJoinContainer { get; }
+
+ public ReactiveCommand<Unit, Unit> DequeueCoin { get; }
- public CoinViewModel(Global global, SmartCoin model)
+ public CoinViewModel(CoinListViewModel owner, Global global, SmartCoin model)
{
Model = model;
+ Owner = owner;
Global = global;
+ InCoinJoinContainer = owner.CoinListContainerType == CoinListContainerType.CoinJoinTabViewModel;
RefreshSmartCoinStatus();
@@ -83,6 +90,12 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
this.RaisePropertyChanged(nameof(AmountBtc));
this.RaisePropertyChanged(nameof(Clusters));
}).DisposeWith(Disposables);
+
+ DequeueCoin = ReactiveCommand.Create(() => Owner.PressDequeue(Model), this.WhenAnyValue(x => x.CoinJoinInProgress));
+
+ DequeueCoin.ThrownExceptions
+ .ObserveOn(RxApp.TaskpoolScheduler)
+ .Subscribe(ex => Logging.Logger.LogError(ex)); // Don't notify about it. Dequeue failure (and success) is notified by other mechanism.
}
public SmartCoin Model { get; }

Put context menu in CoinListView to DataTemplate. Put DequeueCoin function to CoinViewModel. Make sure send tab cannot dequeue.