Remove duplicate code: encapsulate logic for label suggestions inside its own user control and viewmodel.
ADD
WalletWasabi.Gui/Controls/SuggestLabelViewModel.cs
+using ReactiveUI;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reactive.Linq;
+using WalletWasabi.Gui.Tabs.WalletManager;
+using WalletWasabi.Gui.ViewModels;
+
+namespace WalletWasabi.Gui.Controls
+{
+ public class SuggestLabelViewModel : ViewModelBase
+ {
+ private ObservableCollection<SuggestionViewModel> _suggestions;
+ private Global _global;
+ private int _caretIndex;
+ private string _label;
+
+ public SuggestLabelViewModel(Global global)
+ {
+ _global = global;
+
+ _suggestions = new ObservableCollection<SuggestionViewModel>();
+
+ this.WhenAnyValue(x => x.Label)
+ .ObserveOn(RxApp.MainThreadScheduler)
+ .Subscribe(UpdateSuggestions);
+ }
+
+ public void Reset ()
+ {
+ Suggestions = new ObservableCollection<SuggestionViewModel>();
+
+ Label = "";
+ }
+
+ public int CaretIndex
+ {
+ get => _caretIndex;
+ set => this.RaiseAndSetIfChanged(ref _caretIndex, value);
+ }
+
+ public string Label
+ {
+ get => _label;
+ set => this.RaiseAndSetIfChanged(ref _label, value);
+ }
+
+ public ObservableCollection<SuggestionViewModel> Suggestions
+ {
+ get => _suggestions;
+ set => this.RaiseAndSetIfChanged(ref _suggestions, value);
+ }
+
+ private void UpdateSuggestions(string words)
+ {
+ if (string.IsNullOrWhiteSpace(words))
+ {
+ Suggestions?.Clear();
+ return;
+ }
+
+ var enteredWordList = words.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim());
+ var lastWord = enteredWordList?.LastOrDefault()?.Replace("\t", "") ?? "";
+
+ if (!lastWord.Any())
+ {
+ Suggestions.Clear();
+ return;
+ }
+
+ var labels = _global.WalletService.GetLabels();
+ IEnumerable<string> suggestedWords = labels.Where(w => w.StartsWith(lastWord, StringComparison.InvariantCultureIgnoreCase))
+ .Union(labels.Where(w => w.Contains(lastWord, StringComparison.InvariantCultureIgnoreCase)))
+ .Except(enteredWordList)
+ .Take(3);
+
+ Suggestions.Clear();
+ foreach (var suggestion in suggestedWords)
+ {
+ Suggestions.Add(new SuggestionViewModel(suggestion, OnAddWord));
+ }
+ }
+
+ public void OnAddWord(string word)
+ {
+ var words = Label.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray();
+ if (words.Length == 0)
+ {
+ Label = word + ", ";
+ }
+ else
+ {
+ words[^1] = word;
+ Label = string.Join(", ", words) + ", ";
+ }
+
+ CaretIndex = Label.Length;
+
+ Suggestions.Clear();
+ }
+ }
+}

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabView.xaml
WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabView.xaml
<Grid RowDefinitions="Auto,*" DockPanel.Dock="Top">
<DockPanel LastChildFill="True">
<Grid ColumnDefinitions="*,300" DockPanel.Dock="Top" Margin="0 0 0 10">
- <StackPanel Spacing="8">
- <controls:ExtendedTextBox Text="{Binding Label, Mode=TwoWay}" Watermark="Label (Required)" CaretIndex="{Binding CaretIndex}" UseFloatingWatermark="True" Grid.Column="0" Margin="0 0 10 0">
- <i:Interaction.Behaviors>
- <behaviors:SuggestionBehavior SuggestionItems="{Binding Suggestions}" />
- <behaviors:CommandOnEnterBehavior Command="{Binding GenerateCommand}" />
- <behaviors:FocusBehavior IsFocused="{Binding IsSelected}" />
- <iac:FocusOnAttachedToVisualTreeBehavior />
- </i:Interaction.Behaviors>
+ <controls:SuggestLabelView DataContext="{Binding LabelSuggestion}">
<ToolTip.Tip>Who knows this address is yours? E.g.: "Max, BitPay"</ToolTip.Tip>
- </controls:ExtendedTextBox>
- <ItemsControl Items="{Binding Suggestions}">
- <ItemsControl.ItemsPanel>
- <ItemsPanelTemplate>
- <StackPanel Orientation="Horizontal" Spacing="2" />
- </ItemsPanelTemplate>
- </ItemsControl.ItemsPanel>
- <ItemsControl.ItemTemplate>
- <DataTemplate>
- <Button Focusable="False" Content="{Binding Word}" Command="{Binding OnSelected}" IsPressed="{Binding IsHighLighted}" />
- </DataTemplate>
- </ItemsControl.ItemTemplate>
- </ItemsControl>
- </StackPanel>
+ </controls:SuggestLabelView>
<Button Content="Generate Receive Address" Command="{Binding GenerateCommand}" VerticalAlignment="Top" Grid.Column="1" />
</Grid>
</DockPanel>

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabViewModel.cs
@@ -27,11 +27,10 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
private ObservableCollection<AddressViewModel> _addresses;
private AddressViewModel _selectedAddress;
- private string _label;
+ private SuggestLabelViewModel _labelSuggestion;
private double _labelRequiredNotificationOpacity;
private bool _labelRequiredNotificationVisible;
- private int _caretIndex;
- private ObservableCollection<SuggestionViewModel> _suggestions;
+
public ReactiveCommand<Unit, Unit> CopyAddress { get; }
public ReactiveCommand<Unit, Unit> CopyLabel { get; }
@@ -44,15 +43,16 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
public ReceiveTabViewModel(WalletViewModel walletViewModel)
: base("Receive", walletViewModel)
{
+ _labelSuggestion = new SuggestLabelViewModel(Global);
_addresses = new ObservableCollection<AddressViewModel>();
- Label = "";
+ _labelSuggestion.Label = "";
InitializeAddresses();
GenerateCommand = ReactiveCommand.Create(() =>
{
- var label = new SmartLabel(Label);
- Label = label;
+ var label = new SmartLabel(_labelSuggestion.Label);
+ _labelSuggestion.Label = label;
if (label.IsEmpty)
{
NotificationHelpers.Warning("Label is required.");
@@ -62,7 +62,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
Dispatcher.UIThread.PostLogException(() =>
{
- HdPubKey newKey = Global.WalletService.GetReceiveKey(Label, Addresses.Select(x => x.Model).Take(7)); // Never touch the first 7 keys.
+ HdPubKey newKey = Global.WalletService.GetReceiveKey(_labelSuggestion.Label, Addresses.Select(x => x.Model).Take(7)); // Never touch the first 7 keys.
AddressViewModel found = Addresses.FirstOrDefault(x => x.Model == newKey);
if (found != default)
@@ -76,12 +76,10 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
SelectedAddress = newAddress;
- Label = "";
+ _labelSuggestion.Label = "";
});
});
- this.WhenAnyValue(x => x.Label).Subscribe(UpdateSuggestions);
-
this.WhenAnyValue(x => x.SelectedAddress).Subscribe(async address =>
{
if (Global.UiConfig?.Autocopy is false || address is null)
@@ -146,10 +144,10 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
.Merge(SaveQRCodeCommand.ThrownExceptions)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(ex => NotificationHelpers.Error(ex.ToTypeMessageString()));
-
- _suggestions = new ObservableCollection<SuggestionViewModel>();
}
+ public SuggestLabelViewModel LabelSuggestion => _labelSuggestion;
+
private void ToggleSelectedAddress()
{
SelectedAddress.IsExpanded = !SelectedAddress.IsExpanded;
@@ -212,71 +210,5 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
get => _selectedAddress;
set => this.RaiseAndSetIfChanged(ref _selectedAddress, value);
}
-
- public string Label
- {
- get => _label;
- set => this.RaiseAndSetIfChanged(ref _label, value);
- }
-
- public int CaretIndex
- {
- get => _caretIndex;
- set => this.RaiseAndSetIfChanged(ref _caretIndex, value);
- }
-
- public ObservableCollection<SuggestionViewModel> Suggestions
- {
- get => _suggestions;
- set => this.RaiseAndSetIfChanged(ref _suggestions, value);
- }
-
- private void UpdateSuggestions(string words)
- {
- if (string.IsNullOrWhiteSpace(words))
- {
- Suggestions?.Clear();
- return;
- }
-
- var enteredWordList = words.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim());
- var lastWord = enteredWordList?.LastOrDefault()?.Replace("\t", "") ?? "";
-
- if (!lastWord.Any())
- {
- Suggestions.Clear();
- return;
- }
-
- var labels = Global.WalletService.GetLabels();
- IEnumerable<string> suggestedWords = labels.Where(w => w.StartsWith(lastWord, StringComparison.OrdinalIgnoreCase))
- .Union(labels.Where(w => w.Contains(lastWord, StringComparison.OrdinalIgnoreCase)))
- .Except(enteredWordList)
- .Take(3);
-
- Suggestions.Clear();
- foreach (var suggestion in suggestedWords)
- {
- Suggestions.Add(new SuggestionViewModel(suggestion, OnAddWord));
- }
- }
-
- public void OnAddWord(string word)
- {
- var words = Label.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray();
- if (words.Length == 0)
- {
- Label = word + ", ";
- }
- else
- {
- words[^1] = word;
- Label = string.Join(", ", words) + ", ";
- }
-
- CaretIndex = Label.Length;
-
- Suggestions.Clear();
- }
}
}

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/SendTabView.xaml
WalletWasabi.Gui/Controls/WalletExplorer/SendTabView.xaml
<behaviors:PasteAddressOnClickBehavior Command="{Binding OnAddressPasteCommand}" />
</i:Interaction.Behaviors>
</controls:ExtendedTextBox>
- <StackPanel Spacing="8">
- <controls:ExtendedTextBox x:Name="LabelTextBox" Text="{Binding Label}" Watermark="Label" UseFloatingWatermark="True" IsReadOnly="{Binding IsMax}" ToolTip.Tip="{Binding LabelToolTip}" CaretIndex="{Binding CaretIndex}">
- <i:Interaction.Behaviors>
- <behaviors:SuggestionBehavior SuggestionItems="{Binding Suggestions}" />
- <behaviors:CommandOnEnterBehavior Command="{Binding GenerateCommand}" />
- <behaviors:FocusBehavior IsFocused="{Binding IsSelected}" />
- <iac:FocusOnAttachedToVisualTreeBehavior />
- </i:Interaction.Behaviors>
- </controls:ExtendedTextBox>
- <ItemsControl Items="{Binding Suggestions}">
- <ItemsControl.ItemsPanel>
- <ItemsPanelTemplate>
- <StackPanel Orientation="Horizontal" Spacing="2" />
- </ItemsPanelTemplate>
- </ItemsControl.ItemsPanel>
- <ItemsControl.ItemTemplate>
- <DataTemplate>
- <Button Focusable="False" Content="{Binding Word}" Command="{Binding OnSelected}" IsPressed="{Binding IsHighLighted}" />
- </DataTemplate>
- </ItemsControl.ItemTemplate>
- </ItemsControl>
- </StackPanel>
+
+ <controls:SuggestLabelView DataContext="{Binding LabelSuggestion}" />
+
<StackPanel Orientation="Horizontal" Spacing="10">
<Button Content="{Binding IsMax, Converter={StaticResource BooleanStringConverter}, ConverterParameter=Clear:Max}" Command="{Binding MaxCommand}" VerticalAlignment="Top" Height="40" />
<controls:ExtendedTextBox

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/SendTabViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/SendTabViewModel.cs
@@ -6,7 +6,6 @@ using NBitcoin.Payment;
using ReactiveUI;
using System;
using System.Collections.Generic;
-using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Net.Http;
@@ -25,7 +24,6 @@ using WalletWasabi.Blockchain.Transactions;
using WalletWasabi.Exceptions;
using WalletWasabi.Gui.Helpers;
using WalletWasabi.Gui.Models;
-using WalletWasabi.Gui.Tabs.WalletManager;
using WalletWasabi.Gui.ViewModels;
using WalletWasabi.Gui.ViewModels.Validation;
using WalletWasabi.Helpers;
@@ -58,13 +56,11 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
private Money _allSelectedAmount;
private string _password;
private string _address;
- private string _label;
private string _labelToolTip;
private string _feeToolTip;
private string _amountWaterMarkText;
private bool _isBusy;
private bool _isHardwareBusy;
- private int _caretIndex;
private ObservableAsPropertyHelper<bool> _isCustomFee;
private const string SendTransactionButtonTextString = "Send Transaction";
@@ -73,11 +69,12 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
private const string BuildTransactionButtonTextString = "Build Transaction";
private const string BuildingTransactionButtonTextString = "Building Transaction...";
- private ObservableCollection<SuggestionViewModel> _suggestions;
private FeeDisplayFormat _feeDisplayFormat;
private bool _isSliderFeeUsed = true;
private double _feeControlOpacity;
+ private SuggestLabelViewModel _labelSuggestion;
+
private FeeDisplayFormat FeeDisplayFormat
{
get => _feeDisplayFormat;
@@ -90,9 +87,8 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
private void ResetUi()
{
- Suggestions = new ObservableCollection<SuggestionViewModel>();
+ _labelSuggestion.Reset();
Address = "";
- Label = "";
Password = "";
AllSelectedAmount = Money.Zero;
IsMax = false;
@@ -103,6 +99,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
public SendTabViewModel(WalletViewModel walletViewModel, bool isTransactionBuilder = false)
: base(isTransactionBuilder ? "Build Transaction" : "Send", walletViewModel)
{
+ _labelSuggestion = new SuggestLabelViewModel(Global);
IsTransactionBuilder = isTransactionBuilder;
BuildTransactionButtonText = IsTransactionBuilder ? BuildTransactionButtonTextString : SendTransactionButtonTextString;
@@ -175,10 +172,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ => SetSendText());
- this.WhenAnyValue(x => x.Label)
- .ObserveOn(RxApp.MainThreadScheduler)
- .Subscribe(UpdateSuggestions);
-
this.WhenAnyValue(x => x.FeeTarget)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ =>
@@ -220,7 +213,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
SmartLabel label = url.Label;
if (!label.IsEmpty)
{
- Label = label;
+ _labelSuggestion.Label = label;
}
if (url.Amount != null)
@@ -237,11 +230,11 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
IsBusy = true;
MainWindowViewModel.Instance.StatusBar.TryAddStatus(StatusBarStatus.BuildingTransaction);
- var label = new SmartLabel(Label);
- Label = label;
+ var label = new SmartLabel(_labelSuggestion.Label);
+ _labelSuggestion.Label = label;
if (!IsMax && label.IsEmpty)
{
- NotificationHelpers.Warning($"{nameof(Label)} is required.");
+ NotificationHelpers.Warning($"{nameof(_labelSuggestion.Label)} is required.");
return;
}
@@ -456,6 +449,8 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
.Subscribe(ex => NotificationHelpers.Error(ex.ToTypeMessageString()));
}
+ public SuggestLabelViewModel LabelSuggestion => _labelSuggestion;
+
private void SetSendText()
{
if (IsTransactionBuilder)
@@ -816,66 +811,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
set => this.RaiseAndSetIfChanged(ref _password, value);
}
- public int CaretIndex
- {
- get => _caretIndex;
- set => this.RaiseAndSetIfChanged(ref _caretIndex, value);
- }
-
- public ObservableCollection<SuggestionViewModel> Suggestions
- {
- get => _suggestions;
- set => this.RaiseAndSetIfChanged(ref _suggestions, value);
- }
-
- private void UpdateSuggestions(string words)
- {
- if (string.IsNullOrWhiteSpace(words))
- {
- Suggestions?.Clear();
- return;
- }
-
- var enteredWordList = words.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim());
- var lastWord = enteredWordList?.LastOrDefault()?.Replace("\t", "") ?? "";
-
- if (!lastWord.Any())
- {
- Suggestions.Clear();
- return;
- }
-
- var labels = Global.WalletService.GetLabels();
- IEnumerable<string> suggestedWords = labels.Where(w => w.StartsWith(lastWord, StringComparison.InvariantCultureIgnoreCase))
- .Union(labels.Where(w => w.Contains(lastWord, StringComparison.InvariantCultureIgnoreCase)))
- .Except(enteredWordList)
- .Take(3);
-
- Suggestions.Clear();
- foreach (var suggestion in suggestedWords)
- {
- Suggestions.Add(new SuggestionViewModel(suggestion, OnAddWord));
- }
- }
-
- public void OnAddWord(string word)
- {
- var words = Label.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray();
- if (words.Length == 0)
- {
- Label = word + ", ";
- }
- else
- {
- words[^1] = word;
- Label = string.Join(", ", words) + ", ";
- }
-
- CaretIndex = Label.Length;
-
- Suggestions.Clear();
- }
-
public ErrorDescriptors ValidateAddress()
{
if (string.IsNullOrWhiteSpace(Address))
@@ -903,12 +838,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
set => this.RaiseAndSetIfChanged(ref _address, value);
}
- public string Label
- {
- get => _label;
- set => this.RaiseAndSetIfChanged(ref _label, value);
- }
-
public string LabelToolTip
{
get => _labelToolTip;

Remove duplicate code: encapsulate logic for label suggestions inside its own user control and viewmodel.