Send Fee - Advanced options
ADD
WalletWasabi.Fluent/ViewModels/Wallets/Send/SendFeeAdvancedOptionsViewModel.cs
+using System.Globalization;
+using System.Linq;
+using System.Reactive;
+using System.Reactive.Linq;
+using NBitcoin;
+using ReactiveUI;
+using WalletWasabi.Fluent.Validation;
+using WalletWasabi.Fluent.ViewModels.Dialogs.Base;
+using WalletWasabi.Models;
+
+namespace WalletWasabi.Fluent.ViewModels.Wallets.Send;
+
+[NavigationMetaData(Title = "Advanced")]
+public partial class SendFeeAdvancedOptionsViewModel : DialogViewModelBase<FeeRate>
+{
+ private readonly TransactionInfo _transactionInfo;
+
+ [AutoNotify] private string _customFee;
+
+ public SendFeeAdvancedOptionsViewModel(TransactionInfo transactionInfo)
+ {
+ _transactionInfo = transactionInfo;
+
+ _customFee = transactionInfo.IsCustomFeeUsed
+ ? transactionInfo.FeeRate.SatoshiPerByte.ToString(CultureInfo.InvariantCulture)
+ : "";
+
+ EnableBack = false;
+ SetupCancel(enableCancel: true, enableCancelOnEscape: true, enableCancelOnPressed: true);
+
+ this.ValidateProperty(x => x.CustomFee, ValidateCustomFee);
+
+ var nextCommandCanExecute =
+ this.WhenAnyValue(x => x.CustomFee)
+ .Select(_ =>
+ {
+ var noError = !Validations.Any;
+ var somethingFilled = CustomFee is not null or "";
+
+ return noError && somethingFilled;
+ });
+
+ NextCommand = ReactiveCommand.Create(OnNext, nextCommandCanExecute);
+ }
+
+ private void OnNext()
+ {
+ if (decimal.TryParse(CustomFee, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var feeRate))
+ {
+ _transactionInfo.FeeRate = new FeeRate(feeRate);
+ _transactionInfo.IsCustomFeeUsed = true;
+ }
+ else if (_transactionInfo.IsCustomFeeUsed)
+ {
+ _transactionInfo.FeeRate = FeeRate.Zero;
+ _transactionInfo.IsCustomFeeUsed = false;
+ }
+
+ Close(DialogResultKind.Normal, _transactionInfo.FeeRate);
+ }
+
+ private void ValidateCustomFee(IValidationErrors errors)
+ {
+ var customFeeString = CustomFee;
+
+ if (customFeeString is "")
+ {
+ return;
+ }
+
+ if (!decimal.TryParse(customFeeString, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var value))
+ {
+ errors.Add(ErrorSeverity.Error, "The entered fee is not valid.");
+ return;
+ }
+
+ if (value < decimal.One)
+ {
+ errors.Add(ErrorSeverity.Error, "Cannot be less than 1 sat/vByte.");
+ return;
+ }
+
+ try
+ {
+ _ = new FeeRate(value);
+ }
+ catch (OverflowException)
+ {
+ errors.Add(ErrorSeverity.Error, "The entered fee is too high.");
+ return;
+ }
+ }
+}

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/SendFeeViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/SendFeeViewModel.cs
-using System.Globalization;
using System.Linq;
-using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
+using System.Windows.Input;
using NBitcoin;
using ReactiveUI;
using WalletWasabi.Blockchain.Analysis.FeesEstimation;
using WalletWasabi.Fluent.Helpers;
-using WalletWasabi.Fluent.Validation;
using WalletWasabi.Fluent.ViewModels.Dialogs.Base;
-using WalletWasabi.Models;
using WalletWasabi.Wallets;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Send;
@@ -28,9 +25,6 @@ public partial class SendFeeViewModel : DialogViewModelBase<FeeRate>
private readonly Wallet _wallet;
private readonly TransactionInfo _transactionInfo;
private readonly bool _isSilent;
- private bool _isSliderChange;
-
- [AutoNotify] private string _feeRateString;
public SendFeeViewModel(Wallet wallet, TransactionInfo transactionInfo, bool isSilent)
{
@@ -39,118 +33,43 @@ public partial class SendFeeViewModel : DialogViewModelBase<FeeRate>
_wallet = wallet;
_transactionInfo = transactionInfo;
- _feeRateString =
- transactionInfo.IsCustomFeeUsed
- ? transactionInfo.FeeRate.SatoshiPerByte.ToString(CultureInfo.InvariantCulture)
- : "";
-
FeeChart = new FeeChartViewModel();
- FeeChart.WhenAnyValue(x => x.CurrentSatoshiPerByte)
- .Subscribe(OnSliderValueChanged);
-
- this.WhenAnyValue(x => x.FeeRateString)
- .Subscribe(OnFeeRateStringChanged);
-
- this.ValidateProperty(x => x.FeeRateString, ValidateCustomFee);
-
SetupCancel(false, true, false);
EnableBack = true;
- var nextCommandCanExecute =
- this.WhenAnyValue(x => x.FeeRateString)
- .Select(_ =>
- {
- var noError = !Validations.Any;
- var somethingFilled = FeeRateString is not null or "";
-
- return noError && somethingFilled;
- });
+ NextCommand = ReactiveCommand.Create(OnNext);
- NextCommand = ReactiveCommand.Create(OnNext, nextCommandCanExecute);
+ AdvancedOptionsCommand = ReactiveCommand.CreateFromTask(ShowAdvancedOptionsAsync);
}
public FeeChartViewModel FeeChart { get; }
- private void OnSliderValueChanged(decimal x)
- {
- if (_isSliderChange)
- {
- return;
- }
-
- _isSliderChange = true;
-
- _transactionInfo.IsCustomFeeUsed = false;
- _transactionInfo.FeeRate = FeeRate.Zero;
- FeeRateString = x.ToString("0");
- _isSliderChange = false;
- }
-
- private void OnFeeRateStringChanged(string feeRateString)
- {
- if (_isSliderChange)
- {
- return;
- }
-
- if (decimal.TryParse(feeRateString, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var feeRate))
- {
- _transactionInfo.FeeRate = new FeeRate(feeRate);
- _transactionInfo.IsCustomFeeUsed = true;
- _isSliderChange = true;
- FeeChart.InitCurrentConfirmationTarget(_transactionInfo.FeeRate);
- _isSliderChange = false;
- }
- else if (_transactionInfo.IsCustomFeeUsed)
- {
- _transactionInfo.FeeRate = FeeRate.Zero;
- _transactionInfo.IsCustomFeeUsed = false;
- }
- }
+ public ICommand AdvancedOptionsCommand { get; }
- private void ValidateCustomFee(IValidationErrors errors)
+ private void OnNext()
{
- var customFeeString = FeeRateString;
-
- if (!decimal.TryParse(customFeeString, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var value))
+ if (!_transactionInfo.IsCustomFeeUsed)
{
- errors.Add(ErrorSeverity.Error, "The entered fee is not valid.");
- return;
- }
+ _transactionInfo.ConfirmationTimeSpan = TransactionFeeHelper.CalculateConfirmationTime(FeeChart.CurrentConfirmationTarget);
- if (value < decimal.One)
- {
- errors.Add(ErrorSeverity.Error, "Cannot be less than 1 sat/vByte.");
- return;
- }
+ var blockTarget = FeeChart.CurrentConfirmationTarget;
- try
- {
- _ = new FeeRate(value);
+ Services.UiConfig.FeeTarget = (int)blockTarget;
+ Close(DialogResultKind.Normal, new FeeRate(FeeChart.GetSatoshiPerByte(blockTarget)));
}
- catch (OverflowException)
+ else
{
- errors.Add(ErrorSeverity.Error, "The entered fee is too high.");
- return;
+ Close(DialogResultKind.Normal, _transactionInfo.FeeRate);
}
}
- private void OnNext()
- {
- if (_transactionInfo.IsCustomFeeUsed)
+ private async Task ShowAdvancedOptionsAsync()
{
- var feeRate = decimal.Parse(FeeRateString, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture);
- Close(DialogResultKind.Normal, new FeeRate(feeRate));
- }
- else
+ var result = await NavigateDialogAsync(new SendFeeAdvancedOptionsViewModel(_transactionInfo), NavigationTarget.CompactDialogScreen);
+ if (result.Result is { })
{
- _transactionInfo.ConfirmationTimeSpan = TransactionFeeHelper.CalculateConfirmationTime(FeeChart.CurrentConfirmationTarget);
-
- var blockTarget = FeeChart.CurrentConfirmationTarget;
-
- Services.UiConfig.FeeTarget = (int)blockTarget;
- Close(DialogResultKind.Normal, new FeeRate(FeeChart.GetSatoshiPerByte(blockTarget)));
+ Close(DialogResultKind.Normal, result.Result);
}
}

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionPreviewViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/TransactionPreviewViewModel.cs
@@ -182,17 +182,25 @@ public partial class TransactionPreviewViewModel : RoutableViewModel
}
private async Task OnAdjustFeeAsync()
+ {
+ if (_info.IsCustomFeeUsed)
+ {
+ var customFeeResult = await NavigateDialogAsync(new SendFeeAdvancedOptionsViewModel(_info), NavigationTarget.CompactDialogScreen);
+ if (customFeeResult.Kind == DialogResultKind.Normal && customFeeResult.Result is { })
+ {
+ await BuildAndUpdateAsync(BuildTransactionReason.FeeChanged);
+ }
+ }
+ else
{
var feeRateDialogResult = await NavigateDialogAsync(new SendFeeViewModel(_wallet, _info, false));
- if (feeRateDialogResult.Kind == DialogResultKind.Normal && feeRateDialogResult.Result is { } newFeeRate &&
- newFeeRate != _info.FeeRate)
+ if (feeRateDialogResult.Kind == DialogResultKind.Normal && feeRateDialogResult.Result is { })
{
- _info.FeeRate = feeRateDialogResult.Result;
-
await BuildAndUpdateAsync(BuildTransactionReason.FeeChanged);
}
}
+ }
private async Task BuildAndUpdateAsync(BuildTransactionReason reason)
{

ADD
WalletWasabi.Fluent/Views/Wallets/Send/SendFeeAdvancedOptionsView.axaml
+<UserControl xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:c="using:WalletWasabi.Fluent.Controls"
+ xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
+ xmlns:behaviors="using:WalletWasabi.Fluent.Behaviors"
+ xmlns:vm="using:WalletWasabi.Fluent.ViewModels.Wallets.Send"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ x:DataType="vm:SendFeeAdvancedOptionsViewModel"
+ x:CompileBindings="True"
+ x:Class="WalletWasabi.Fluent.Views.Wallets.Send.SendFeeAdvancedOptionsView">
+ <c:ContentArea Title="{Binding Title}" Caption=""
+ EnableBack="{Binding EnableBack}"
+ EnableNext="True"
+ NextContent="Done"
+ EnableCancel="{Binding EnableCancel}"
+ IsBusy="{Binding IsBusy}">
+ <StackPanel Orientation="Horizontal" Spacing="20" VerticalAlignment="Top">
+ <Label Content="_Transaction fee rate:" Target="CustomFee" />
+ <c:CurrencyEntryBox Text="{Binding CustomFee}" CurrencyCode="sat/vbyte"
+ Width="220" MaxLength="10" Classes="standalone"
+ Watermark="0 sat/vbyte" x:Name="CustomFee">
+ <i:Interaction.Behaviors>
+ <behaviors:FocusOnAttachedBehavior />
+ </i:Interaction.Behaviors>
+ </c:CurrencyEntryBox>
+ </StackPanel>
+ </c:ContentArea>
+</UserControl>

ADD
WalletWasabi.Fluent/Views/Wallets/Send/SendFeeAdvancedOptionsView.axaml.cs
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace WalletWasabi.Fluent.Views.Wallets.Send;
+public partial class SendFeeAdvancedOptionsView : UserControl
+{
+ public SendFeeAdvancedOptionsView()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+}

MODIFY
WalletWasabi.Fluent/Views/Wallets/Send/SendFeeView.axaml
WalletWasabi.Fluent/Views/Wallets/Send/SendFeeView.axaml
EnableNext="True" NextContent="Continue"
IsBusy="{Binding IsBusy}">
<DockPanel>
- <StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" HorizontalAlignment="Center" Spacing="20">
- <StackPanel Orientation="Horizontal">
- <Label Content="_Fee rate:" Target="FeeRate" Margin="0 10 5 0" />
- <c:CurrencyEntryBox Text="{Binding FeeRateString}" CurrencyCode="sat/vbyte"
- Width="220" MaxLength="10" Classes="standalone"
- Watermark="0 sat/vbyte"/>
- </StackPanel>
- <StackPanel Orientation="Horizontal">
- <Label Content="{Binding FeeChart.CurrentConfirmationTargetString, Mode=OneWay, StringFormat='Estimated Time: \{0\}'}"
- Margin="0 10 0 0" MinWidth="200" />
- </StackPanel>
- </StackPanel>
+
+ <!-- Advanced -->
+ <Button Classes="h8 plain activeHyperLink" Margin="0 10 0 0"
+ Command="{Binding AdvancedOptionsCommand}"
+ DockPanel.Dock="Bottom"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Bottom"
+ Content="Advanced options" />
<DockPanel MinHeight="80" DataContext="{Binding FeeChart}">
<DockPanel.Resources>
<Thickness x:Key="SliderTopHeaderMargin">0,0,0,4</Thickness>
Value="{Binding SliderValue}">
<Slider.Styles>
<Style Selector="Slider /template/ Thumb">
+ <Setter Property="ToolTip.Tip">
+ <Template>
+ <ToolTip>
+ <StackPanel Spacing="10" x:CompileBindings="False">
+ <TextBlock Text="{Binding $parent[Slider].DataContext.CurrentSatoshiPerByte, Mode=OneWay, StringFormat='Fee Rate: \{0:0\} sat/vByte'}" />
+ <TextBlock Text="{Binding $parent[Slider].DataContext.CurrentConfirmationTargetString, Mode=OneWay, StringFormat='Estimated Time: \{0\}'}" />
+ </StackPanel>
+ </ToolTip>
+ </Template>
+ </Setter>
<Setter Property="ToolTip.Placement" Value="Bottom" />
<Setter Property="ToolTip.VerticalOffset" Value="10" />
<Setter Property="ToolTip.HorizontalOffset" Value="-30" />

Send Fee - Advanced options