Skip to content

Commit

Permalink
Minimal Decoupling of SendViewModel and TransactionPreview
Browse files Browse the repository at this point in the history
  • Loading branch information
ichthus1604 committed Apr 17, 2024
1 parent c8b1231 commit e23b106
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 71 deletions.
18 changes: 18 additions & 0 deletions WalletWasabi.Fluent/Models/Transactions/SendParameters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Collections.Generic;
using WalletWasabi.Blockchain.TransactionOutputs;
using WalletWasabi.Fluent.ViewModels.Wallets.Send;
using WalletWasabi.Wallets;

namespace WalletWasabi.Fluent.Models.Transactions;

public record SendParameters(
Wallet Wallet,
ICoinsView AvailableCoins,
TransactionInfo? TransactionInfo = null)
{
public static SendParameters Create(Wallet wallet) => new SendParameters(wallet, wallet.Coins);

public static SendParameters CreateManual(Wallet wallet, IEnumerable<SmartCoin> coins) => new SendParameters(wallet, new CoinsView(coins));

public decimal AvailableAmountBtc => AvailableCoins.TotalAmount().ToDecimal(NBitcoin.MoneyUnit.BTC);
}
5 changes: 4 additions & 1 deletion WalletWasabi.Fluent/Models/Wallets/AmountProvider.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using NBitcoin;
using ReactiveUI;
using System.Reactive.Linq;
using WalletWasabi.Services;

namespace WalletWasabi.Fluent.Models.Wallets;
Expand All @@ -13,7 +14,9 @@ public partial class AmountProvider : ReactiveObject
public AmountProvider(WasabiSynchronizer synchronizer)
{
_synchronizer = synchronizer;
BtcToUsdExchangeRates = this.WhenAnyValue(provider => provider._synchronizer.UsdExchangeRate);
BtcToUsdExchangeRates =
this.WhenAnyValue(provider => provider._synchronizer.UsdExchangeRate)
.ObserveOn(RxApp.MainThreadScheduler);

BtcToUsdExchangeRates.BindTo(this, x => x.UsdExchangeRate);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ private async Task OnSendCoinsAsync()
};

// TODO: Remove this after TransactionPreviewViewModel is decoupled.
var wallet = MainViewModel.Instance.NavBar.Wallets.First(x => x.Wallet.WalletName == _wallet.Name).WalletViewModel;
Navigate().To().TransactionPreview(wallet, info);
var walletVm = MainViewModel.Instance.NavBar.Wallets.First(x => x.Wallet.WalletName == _wallet.Name).WalletViewModel;
Navigate().To().TransactionPreview(walletVm.Wallet, _wallet, info);
}
}
118 changes: 60 additions & 58 deletions WalletWasabi.Fluent/ViewModels/Wallets/Send/SendViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
using WalletWasabi.Fluent.ViewModels.Wallets.Labels;
using WalletWasabi.Userfacing.Bip21;
using Constants = WalletWasabi.Helpers.Constants;
using WalletWasabi.Fluent.Models.Transactions;

namespace WalletWasabi.Fluent.ViewModels.Wallets.Send;

Expand All @@ -44,6 +45,8 @@ public partial class SendViewModel : RoutableViewModel
{
private readonly object _parsingLock = new();
private readonly Wallet _wallet;
private readonly IWalletModel _walletModel;
private readonly SendParameters _parameters;
private readonly CoinJoinManager? _coinJoinManager;
private readonly ClipboardObserver _clipboardObserver;

Expand All @@ -58,22 +61,23 @@ public partial class SendViewModel : RoutableViewModel
[AutoNotify] private bool _conversionReversed;
[AutoNotify(SetterModifier = AccessModifier.Private)] private SuggestionLabelsViewModel _suggestionLabels;

public SendViewModel(UiContext uiContext, WalletViewModel walletVm)
public SendViewModel(UiContext uiContext, IWalletModel walletModel, SendParameters parameters)
{
UiContext = uiContext;
WalletVm = walletVm;
_to = "";

_wallet = walletVm.Wallet;
_wallet = parameters.Wallet;
_walletModel = walletModel;
_parameters = parameters;
_coinJoinManager = Services.HostedServices.GetOrDefault<CoinJoinManager>();

_conversionReversed = Services.UiConfig.SendAmountConversionReversed;

ExchangeRate = _wallet.Synchronizer.UsdExchangeRate;
ExchangeRate = _walletModel.AmountProvider.UsdExchangeRate;

Balance = walletVm.WalletModel.Balances;
Balance = _walletModel.Balances;

_suggestionLabels = new SuggestionLabelsViewModel(WalletVm.WalletModel, Intent.Send, 3);
_suggestionLabels = new SuggestionLabelsViewModel(_walletModel, Intent.Send, 3);

SetupCancel(enableCancel: true, enableCancelOnEscape: true, enableCancelOnPressed: true);

Expand All @@ -90,17 +94,9 @@ public SendViewModel(UiContext uiContext, WalletViewModel walletVm)
.Subscribe(endPoint => IsPayJoin = endPoint is { });

PasteCommand = ReactiveCommand.CreateFromTask(async () => await OnPasteAsync());
AutoPasteCommand = ReactiveCommand.CreateFromTask(async () => await OnAutoPasteAsync());
InsertMaxCommand = ReactiveCommand.Create(() => AmountBtc = _wallet.Coins.TotalAmount().ToDecimal(MoneyUnit.BTC));
QrCommand = ReactiveCommand.Create(async () =>
{
ShowQrCameraDialogViewModel dialog = new(UiContext, _wallet.Network);
var result = await NavigateDialogAsync(dialog, NavigationTarget.CompactDialogScreen);
if (!string.IsNullOrWhiteSpace(result.Result))
{
To = result.Result;
}
});
AutoPasteCommand = ReactiveCommand.CreateFromTask(OnAutoPasteAsync);
InsertMaxCommand = ReactiveCommand.Create(() => AmountBtc = parameters.AvailableCoins.TotalAmount().ToDecimal(MoneyUnit.BTC));
QrCommand = ReactiveCommand.Create(ShowQrCameraAsync);

var nextCommandCanExecute =
this.WhenAnyValue(
Expand All @@ -117,34 +113,7 @@ public SendViewModel(UiContext uiContext, WalletViewModel walletVm)
return allFilled && !hasError && (labelsCount > 0 || isCurrentTextValid);
});

NextCommand = ReactiveCommand.CreateFromTask(
async () =>
{
var label = new LabelsArray(SuggestionLabels.Labels.ToArray());
if (AmountBtc is not { } amountBtc)
{
return;
}
var amount = new Money(amountBtc, MoneyUnit.BTC);
var transactionInfo = new TransactionInfo(BitcoinAddress.Create(To, _wallet.Network), _wallet.AnonScoreTarget)
{
Amount = amount,
Recipient = label,
PayJoinClient = GetPayjoinClient(PayJoinEndPoint),
IsFixedAmount = IsFixedAmount,
SubtractFee = amount == _wallet.Coins.TotalAmount() && !(IsFixedAmount || IsPayJoin)
};
if (_coinJoinManager is { } coinJoinManager)
{
await coinJoinManager.WalletEnteredSendingAsync(_wallet);
}
Navigate().To().TransactionPreview(walletVm, transactionInfo);
},
nextCommandCanExecute);
NextCommand = ReactiveCommand.CreateFromTask(OnNextAsync, nextCommandCanExecute);

this.WhenAnyValue(x => x.ConversionReversed)
.Skip(1)
Expand All @@ -155,8 +124,6 @@ public SendViewModel(UiContext uiContext, WalletViewModel walletVm)

public IObservable<Amount> Balance { get; }

public WalletViewModel WalletVm { get; }

public IObservable<string?> UsdContent => _clipboardObserver.ClipboardUsdContentChanged(RxApp.MainThreadScheduler);

public IObservable<string?> BitcoinContent => _clipboardObserver.ClipboardBtcContentChanged(RxApp.MainThreadScheduler);
Expand All @@ -171,9 +138,36 @@ public SendViewModel(UiContext uiContext, WalletViewModel walletVm)

public ICommand InsertMaxCommand { get; }

private async Task OnNextAsync()
{
var label = new LabelsArray(SuggestionLabels.Labels.ToArray());

if (AmountBtc is not { } amountBtc)
{
return;
}

var amount = new Money(amountBtc, MoneyUnit.BTC);
var transactionInfo = new TransactionInfo(BitcoinAddress.Create(To, _walletModel.Network), _walletModel.Settings.AnonScoreTarget)
{
Amount = amount,
Recipient = label,
PayJoinClient = GetPayjoinClient(PayJoinEndPoint),
IsFixedAmount = IsFixedAmount,
SubtractFee = amount == _wallet.Coins.TotalAmount() && !(IsFixedAmount || IsPayJoin)
};

if (_coinJoinManager is { } coinJoinManager)
{
await coinJoinManager.WalletEnteredSendingAsync(_wallet);
}

Navigate().To().TransactionPreview(_wallet, _walletModel, transactionInfo);
}

private async Task OnAutoPasteAsync()
{
var isAutoPasteEnabled = Services.UiConfig.AutoPaste;
var isAutoPasteEnabled = UiContext.ApplicationSettings.AutoPaste;

if (string.IsNullOrEmpty(To) && isAutoPasteEnabled)
{
Expand Down Expand Up @@ -222,6 +216,15 @@ private async Task OnPasteAsync(bool pasteIfInvalid = true)
return null;
}

private async Task ShowQrCameraAsync()
{
var result = await Navigate().To().ShowQrCameraDialog(_walletModel.Network).GetResultAsync();
if (!string.IsNullOrWhiteSpace(result))
{
To = result;
}
}

private void ValidateAmount(IValidationErrors errors)
{
if (AmountBtc is null)
Expand All @@ -233,7 +236,7 @@ private void ValidateAmount(IValidationErrors errors)
{
errors.Add(ErrorSeverity.Error, "Amount must be less than the total supply of BTC.");
}
else if (AmountBtc > _wallet.Coins.TotalAmount().ToDecimal(MoneyUnit.BTC))
else if (AmountBtc > _parameters.AvailableAmountBtc)
{
errors.Add(ErrorSeverity.Error, "Insufficient funds to cover the amount requested.");
}
Expand All @@ -245,11 +248,11 @@ private void ValidateAmount(IValidationErrors errors)

private void ValidateToField(IValidationErrors errors)
{
if (!string.IsNullOrEmpty(To) && (To.IsTrimmable() || !AddressStringParser.TryParse(To, _wallet.Network, out _)))
if (!string.IsNullOrEmpty(To) && (To.IsTrimmable() || !AddressStringParser.TryParse(To, _walletModel.Network, out _)))
{
errors.Add(ErrorSeverity.Error, "Input a valid BTC address or URL.");
}
else if (IsPayJoin && _wallet.KeyManager.IsHardwareWallet)
else if (IsPayJoin && _walletModel.IsHardwareWallet)
{
errors.Add(ErrorSeverity.Error, "Payjoin is not possible with hardware wallets.");
}
Expand Down Expand Up @@ -284,7 +287,7 @@ private bool TryParseUrl(string? text)

bool result = false;

if (AddressStringParser.TryParse(text, _wallet.Network, out Bip21UriParser.Result? parserResult))
if (AddressStringParser.TryParse(text, _walletModel.Network, out Bip21UriParser.Result? parserResult))
{
result = true;

Expand All @@ -308,7 +311,7 @@ private bool TryParseUrl(string? text)
if (parserResult.Label is { } parsedLabel)
{
SuggestionLabels = new SuggestionLabelsViewModel(
WalletVm.WalletModel,
_walletModel,
Intent.Send,
3,
[parsedLabel]);
Expand All @@ -335,16 +338,15 @@ protected override void OnNavigatedTo(bool inHistory, CompositeDisposable dispos

if (_coinJoinManager is { } coinJoinManager)
{
coinJoinManager.WalletEnteredSendWorkflow(_wallet.WalletId);
coinJoinManager.WalletEnteredSendWorkflow(_walletModel.Id);
}
}

_suggestionLabels.Activate(disposables);

_wallet.Synchronizer.WhenAnyValue(x => x.UsdExchangeRate)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x => ExchangeRate = x)
.DisposeWith(disposables);
_walletModel.AmountProvider.BtcToUsdExchangeRates
.BindTo(this, x => x.ExchangeRate)
.DisposeWith(disposables);

RxApp.MainThreadScheduler.Schedule(async () => await OnAutoPasteAsync());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
{
private readonly Stack<(BuildTransactionResult, TransactionInfo)> _undoHistory;
private readonly Wallet _wallet;
private readonly WalletViewModel _walletViewModel;
private readonly IWalletModel _walletModel;
private TransactionInfo _info;
private TransactionInfo _currentTransactionInfo;
private CancellationTokenSource _cancellationTokenSource;
Expand All @@ -41,11 +41,12 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
[AutoNotify] private bool _canUndo;
[AutoNotify] private bool _isCoinControlVisible;

public TransactionPreviewViewModel(UiContext uiContext, WalletViewModel walletViewModel, TransactionInfo info)
public TransactionPreviewViewModel(UiContext uiContext, Wallet wallet, IWalletModel walletModel, TransactionInfo info)
{
_undoHistory = new();
_wallet = walletViewModel.Wallet;
_walletViewModel = walletViewModel;
_wallet = wallet;
_walletModel = walletModel;

_info = info;
_currentTransactionInfo = info.Clone();
_cancellationTokenSource = new CancellationTokenSource();
Expand Down Expand Up @@ -180,9 +181,9 @@ private async Task BuildAndUpdateAsync()

private async Task OnChangeCoinsAsync()
{
var currentCoins = _walletViewModel.WalletModel.Coins.GetSpentCoins(Transaction);
var currentCoins = _walletModel.Coins.GetSpentCoins(Transaction);

var selectedCoins = await Navigate().To().SelectCoinsDialog(_walletViewModel.WalletModel, currentCoins, _info).GetResultAsync();
var selectedCoins = await Navigate().To().SelectCoinsDialog(_walletModel, currentCoins, _info).GetResultAsync();

if (selectedCoins is { })
{
Expand Down Expand Up @@ -420,13 +421,12 @@ await ShowErrorAsync(

private async Task<bool> AuthorizeAsync(TransactionAuthorizationInfo transactionAuthorizationInfo)
{
if (!_wallet.KeyManager.IsHardwareWallet &&
string.IsNullOrEmpty(_wallet.Kitchen.SaltSoup())) // Do not show authentication dialog when password is empty
if (!_walletModel.IsHardwareWallet && !_walletModel.Auth.HasPassword) // Do not show authentication dialog when password is empty
{
return true;
}

var authDialog = AuthorizationHelpers.GetAuthorizationDialog(_walletViewModel.WalletModel, transactionAuthorizationInfo);
var authDialog = AuthorizationHelpers.GetAuthorizationDialog(_walletModel, transactionAuthorizationInfo);
var authDialogResult = await NavigateDialogAsync(authDialog, authDialog.DefaultTarget, NavigationMode.Clear);

return authDialogResult.Result;
Expand Down
3 changes: 2 additions & 1 deletion WalletWasabi.Fluent/ViewModels/Wallets/WalletViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using ReactiveUI;
using WalletWasabi.Fluent.Extensions;
using WalletWasabi.Fluent.Infrastructure;
using WalletWasabi.Fluent.Models.Transactions;
using WalletWasabi.Fluent.Models.UI;
using WalletWasabi.Fluent.Models.Wallets;
using WalletWasabi.Fluent.ViewModels.Navigation;
Expand Down Expand Up @@ -79,7 +80,7 @@ public WalletViewModel(UiContext uiContext, IWalletModel walletModel, Wallet wal
return (isSelected && !isWalletBalanceZero && (!areAllCoinsPrivate || pointerOver)) && !WalletModel.IsWatchOnlyWallet;
});

SendCommand = ReactiveCommand.Create(() => Navigate().To().Send(this));
SendCommand = ReactiveCommand.Create(() => Navigate().To().Send(walletModel, SendParameters.Create(wallet)));

ReceiveCommand = ReactiveCommand.Create(() => Navigate().To().Receive(WalletModel));

Expand Down

0 comments on commit e23b106

Please sign in to comment.