Integrate into send flow
MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Advanced/WalletCoins/WalletCoinsViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Advanced/WalletCoins/WalletCoinsViewModel.cs
@@ -131,7 +131,7 @@ public partial class WalletCoinsViewModel : RoutableViewModel
IsFixedAmount = true,
};
- Navigate().To(new TransactionPreviewViewModel(wallet, info));
+ Navigate().To(new TransactionPreviewViewModel(_walletVm, info));
}
private FlatTreeDataGridSource<WalletCoinViewModel> CreateGridSource(IEnumerable<WalletCoinViewModel> coins)

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/SendViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/SendViewModel.cs
@@ -23,9 +23,6 @@ using Constants = WalletWasabi.Helpers.Constants;
using WalletWasabi.Fluent.ViewModels.Dialogs;
using WalletWasabi.WabiSabi.Client;
using WalletWasabi.Fluent.ViewModels.Wallets.Home.Tiles;
-using System.Reactive;
-using System.Collections.ObjectModel;
-using WalletWasabi.Fluent.ViewModels.Wallets.Home.History.HistoryItems;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Send;
@@ -50,6 +47,7 @@ public partial class SendViewModel : RoutableViewModel
[AutoNotify] private bool _isPayJoin;
[AutoNotify] private string? _payJoinEndPoint;
[AutoNotify] private bool _conversionReversed;
+ [AutoNotify] private bool _isAutomaticSelectionEnabled = true;
public SendViewModel(WalletViewModel walletVm)
{
@@ -103,7 +101,8 @@ public partial class SendViewModel : RoutableViewModel
return allFilled && !hasError;
});
- NextCommand = ReactiveCommand.CreateFromTask(async () =>
+ NextCommand = ReactiveCommand.CreateFromTask(
+ async () =>
{
var labelDialog = new LabelEntryDialogViewModel(_wallet, _parsedLabel);
var result = await NavigateDialogAsync(labelDialog, NavigationTarget.CompactDialogScreen);
@@ -117,17 +116,34 @@ public partial class SendViewModel : RoutableViewModel
Amount = new Money(AmountBtc, MoneyUnit.BTC),
Recipient = label,
PayJoinClient = PayJoinEndPoint is { } ? GetPayjoinClient(PayJoinEndPoint) : null,
- IsFixedAmount = _isFixedAmount
+ IsFixedAmount = _isFixedAmount,
+ IsAutomaticSelectionEnabled = IsAutomaticSelectionEnabled
};
- Navigate().To(new TransactionPreviewViewModel(_wallet, transactionInfo));
- }, nextCommandCanExecute);
+ Navigate().To(new TransactionPreviewViewModel(walletVm, transactionInfo));
+ },
+ nextCommandCanExecute);
+
+ ToggleCoinControlCommand = ReactiveCommand.Create(() => IsAutomaticSelectionEnabled = !IsAutomaticSelectionEnabled);
this.WhenAnyValue(x => x.ConversionReversed)
.Skip(1)
.Subscribe(x => Services.UiConfig.SendAmountConversionReversed = x);
}
+ public bool IsCoinControlEnabled
+ {
+ get
+ {
+ {
+#if DEBUG
+ return true;
+#endif
+ return false;
+ }
+ }
+ }
+
public bool IsQrButtonVisible { get; }
public ICommand PasteCommand { get; }
@@ -138,6 +154,8 @@ public partial class SendViewModel : RoutableViewModel
public ICommand InsertMaxCommand { get; }
+ public ICommand ToggleCoinControlCommand { get; }
+
public WalletBalanceTileViewModel Balance { get; }
private async Task OnAutoPasteAsync()

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionInfo.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionInfo.cs
@@ -61,6 +61,8 @@ public partial class TransactionInfo
public bool IsFixedAmount { get; init; }
+ public bool IsAutomaticSelectionEnabled { get; set; }
+
private void OnFeeChanged()
{
ChangelessCoins = Enumerable.Empty<SmartCoin>();

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionPreviewViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionPreviewViewModel.cs
@@ -16,6 +16,7 @@ using WalletWasabi.Extensions;
using WalletWasabi.Fluent.Extensions;
using WalletWasabi.Fluent.Helpers;
using WalletWasabi.Fluent.Models;
+using WalletWasabi.Fluent.ViewModels.CoinControl;
using WalletWasabi.Fluent.ViewModels.Dialogs.Base;
using WalletWasabi.Fluent.ViewModels.Navigation;
using WalletWasabi.Logging;
@@ -36,11 +37,13 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
[AutoNotify] private bool _adjustFeeAvailable;
[AutoNotify] private TransactionSummaryViewModel? _displayedTransactionSummary;
[AutoNotify] private bool _canUndo;
+ private readonly WalletViewModel _walletViewModel;
- public TransactionPreviewViewModel(Wallet wallet, TransactionInfo info)
+ public TransactionPreviewViewModel(WalletViewModel walletViewModel, TransactionInfo info)
{
_undoHistory = new();
- _wallet = wallet;
+ _walletViewModel = walletViewModel;
+ _wallet = _walletViewModel.Wallet;
_info = info;
_currentTransactionInfo = info.Clone();
_cancellationTokenSource = new CancellationTokenSource();
@@ -199,20 +202,20 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
}
}
- private async Task BuildAndUpdateAsync(BuildTransactionReason reason)
+ private async Task BuildAndUpdateAsync(BuildTransactionReason reason, bool addToUndoHistory = true)
{
var newTransaction = await BuildTransactionAsync(reason);
if (newTransaction is { })
{
- UpdateTransaction(CurrentTransactionSummary, newTransaction);
+ UpdateTransaction(CurrentTransactionSummary, newTransaction, addToUndoHistory);
}
}
private async Task OnChangePocketsAsync()
{
- var selectPocketsDialog =
- await NavigateDialogAsync(new PrivacyControlViewModel(_wallet, _info, Transaction?.SpentCoins, false));
+ var coinSelectionDialog = GetCoinSelectionDialog();
+ var selectPocketsDialog = await NavigateDialogAsync(coinSelectionDialog);
if (selectPocketsDialog.Kind == DialogResultKind.Normal && selectPocketsDialog.Result is { })
{
@@ -365,18 +368,25 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
}
else
{
- var doSilentPocketSelection = reason == BuildTransactionReason.Initialization;
_info.MinimumRequiredAmount = minimumRequiredAmount;
- var selectPocketsDialog =
- await NavigateDialogAsync(new PrivacyControlViewModel(_wallet, _info, usedCoins: Transaction?.SpentCoins, isSilent: doSilentPocketSelection));
+ DialogResult<IEnumerable<SmartCoin>> dialogResult;
+ if (reason == BuildTransactionReason.Initialization)
+ {
+ dialogResult = await NavigateDialogAsync(new PrivacyControlViewModel(_wallet, _info, usedCoins: Transaction?.SpentCoins, isSilent: true));
+ }
+ else
+ {
+ var coinSelectionDialog = GetCoinSelectionDialog();
+ dialogResult = await NavigateDialogAsync(coinSelectionDialog);
+ }
- if (selectPocketsDialog.Kind == DialogResultKind.Normal && selectPocketsDialog.Result is { })
+ if (dialogResult.Kind == DialogResultKind.Normal && dialogResult.Result is { })
{
- _info.Coins = selectPocketsDialog.Result;
+ _info.Coins = dialogResult.Result;
return true;
}
- else if (selectPocketsDialog.Kind != DialogResultKind.Normal)
+ else if (dialogResult.Kind != DialogResultKind.Normal)
{
return false;
}
@@ -396,6 +406,21 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
if (await BuildTransactionAsync(BuildTransactionReason.Initialization) is { } initialTransaction)
{
UpdateTransaction(CurrentTransactionSummary, initialTransaction);
+
+ if (!_info.IsAutomaticSelectionEnabled)
+ {
+ var coinSelectionDialogResult = await NavigateDialogAsync(new SelectCoinsDialogViewModel(_walletViewModel, _info, Transaction?.SpentCoins));
+
+ if (coinSelectionDialogResult.Kind == DialogResultKind.Normal && coinSelectionDialogResult.Result is { })
+ {
+ _info.Coins = coinSelectionDialogResult.Result;
+ await BuildAndUpdateAsync(BuildTransactionReason.PocketChanged, addToUndoHistory: false);
+ }
+ else
+ {
+ Navigate().Back();
+ }
+ }
}
else
{
@@ -524,4 +549,11 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
_info.IsOtherPocketSelectionPossible = labelSelection.IsOtherSelectionPossible(usedCoins, _info.Recipient);
}
+
+ private DialogViewModelBase<IEnumerable<SmartCoin>> GetCoinSelectionDialog()
+ {
+ return _info.IsAutomaticSelectionEnabled
+ ? new PrivacyControlViewModel(_wallet, _info, Transaction?.SpentCoins, isSilent: false)
+ : new SelectCoinsDialogViewModel(_walletViewModel, _info, Transaction?.SpentCoins);
+ }
}

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionSummaryViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionSummaryViewModel.cs
using System.Linq;
-using NBitcoin;
using ReactiveUI;
using WalletWasabi.Blockchain.Analysis.Clustering;
using WalletWasabi.Blockchain.TransactionBuilding;
@@ -20,7 +19,7 @@ public partial class TransactionSummaryViewModel : ViewModelBase
[AutoNotify] private string _feeText = "";
[AutoNotify] private bool _maxPrivacy;
[AutoNotify] private bool _isCustomFeeUsed;
- [AutoNotify] private bool _isOtherPocketSelectionPossible;
+ [AutoNotify] private bool _isEditCoinsEnabled;
[AutoNotify] private SmartLabel _labels = SmartLabel.Empty;
[AutoNotify] private SmartLabel _recipient = SmartLabel.Empty;
@@ -74,6 +73,8 @@ public partial class TransactionSummaryViewModel : ViewModelBase
Recipient = info.Recipient;
IsCustomFeeUsed = info.IsCustomFeeUsed;
- IsOtherPocketSelectionPossible = info.IsOtherPocketSelectionPossible;
+ IsEditCoinsEnabled = info.IsAutomaticSelectionEnabled
+ ? info.IsOtherPocketSelectionPossible
+ : _transaction.SpentCoins.Sum(x => x.Amount) != _wallet.Coins.TotalAmount();
}
}

MODIFY
WalletWasabi.Fluent/Views/Wallets/Send/SendView.axaml
WalletWasabi.Fluent/Views/Wallets/Send/SendView.axaml
IsConversionReversed="{Binding ConversionReversed, Mode=TwoWay}" />
</DockPanel>
</DockPanel>
+
+ <!-- Coin control -->
+ <StackPanel IsVisible="{Binding IsCoinControlEnabled}" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0 -30 0 0" DockPanel.Dock="Top" Opacity="0.6" Spacing="6">
+ <PathIcon Data="{StaticResource info_regular}" Width="14" VerticalAlignment="Center" xml:space="preserve" ToolTip.Tip="Automatic: Wasabi Wallet will choose the coins for you.&#x0d;&#x0a;Manual: You'll be able to select which coins to use." />
+ <Button Focusable="False" Classes="plainHyperlink h8" Command="{Binding ToggleCoinControlCommand}">
+ <Panel>
+ <TextBlock Text="Coin control: Automatic" IsVisible="{Binding IsAutomaticSelectionEnabled}" />
+ <TextBlock Text="Coin control: Manual" IsVisible="{Binding !IsAutomaticSelectionEnabled}" />
+ </Panel>
+ </Button>
+ </StackPanel>
+
</DockPanel>
</c:ContentArea>
</UserControl>

MODIFY
WalletWasabi.Fluent/Views/Wallets/Send/TransactionSummary.axaml
WalletWasabi.Fluent/Views/Wallets/Send/TransactionSummary.axaml
ToolTip.Tip="Control your privacy"
DockPanel.Dock="Right"
Margin="10 0"
- IsVisible="{Binding IsOtherPocketSelectionPossible}">
+ IsVisible="{Binding IsEditCoinsEnabled}">
<Viewbox Height="20">
<PathIcon Data="{StaticResource entities_edit_regular}" Foreground="{DynamicResource SystemAccentColor}" />
</Viewbox>

Integrate into send flow