Refactor suggested labels to separate view
MODIFY
WalletWasabi.Fluent/Controls/TagsBox.axaml.cs
WalletWasabi.Fluent/Controls/TagsBox.axaml.cs
@@ -70,7 +70,10 @@ namespace WalletWasabi.Fluent.Controls
(o, v) => o.CompletedCommand = v);
public static readonly StyledProperty<bool> IsReadOnlyProperty =
- AvaloniaProperty.Register<TagsBox, bool>("IsReadOnly");
+ AvaloniaProperty.Register<TagsBox, bool>(nameof(IsReadOnly));
+
+ public static readonly StyledProperty<Action<Action<string>>?> SetAddTagActionProperty =
+ AvaloniaProperty.Register<TagsBox, Action<Action<string>>?>(nameof(SetAddTagAction));
[Content]
public IEnumerable<string>? Items
@@ -133,6 +136,12 @@ namespace WalletWasabi.Fluent.Controls
set => SetValue(AllowDuplicationProperty, value);
}
+ public Action<Action<string>>? SetAddTagAction
+ {
+ get => GetValue(SetAddTagActionProperty);
+ set => SetValue(SetAddTagActionProperty, value);
+ }
+
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
@@ -456,6 +465,10 @@ namespace WalletWasabi.Fluent.Controls
? StringComparison.CurrentCulture
: StringComparison.CurrentCultureIgnoreCase;
}
+ else if (e.Property == SetAddTagActionProperty)
+ {
+ SetAddTagAction?.Invoke(AddTag);
+ }
}
internal void RemoveTargetTag(object? tag)

ADD
WalletWasabi.Fluent/ViewModels/Wallets/Labels/SuggestionLabelViewModel.cs
+using System;
+using System.Windows.Input;
+using ReactiveUI;
+
+namespace WalletWasabi.Fluent.ViewModels.Wallets.Labels
+{
+ public partial class SuggestionLabelViewModel : ViewModelBase
+ {
+ public SuggestionLabelViewModel(string label, int count, Action<string> addTag)
+ {
+ Label = label;
+ Count = count;
+ AddTagCommand = ReactiveCommand.Create(addTag);
+ }
+
+ public string Label { get; }
+
+ public int Count { get; }
+
+ public ICommand AddTagCommand { get; }
+ }
+}
\ No newline at end of file

ADD
WalletWasabi.Fluent/ViewModels/Wallets/Labels/SuggestionLabelsViewModel.cs
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reactive;
+using System.Reactive.Linq;
+using DynamicData;
+using DynamicData.Binding;
+using ReactiveUI;
+using WalletWasabi.Fluent.Helpers;
+
+namespace WalletWasabi.Fluent.ViewModels.Wallets.Labels
+{
+ public partial class SuggestionLabelsViewModel
+ {
+ private Action<string>? _addTag;
+ private readonly SourceList<SuggestionLabelViewModel> _suggestionLabels;
+ private readonly ObservableCollectionExtended<SuggestionLabelViewModel> _suggestionLabelResults;
+ private readonly ObservableCollectionExtended<string> _labels;
+ [AutoNotify] private HashSet<string> _suggestions;
+
+ public SuggestionLabelsViewModel()
+ {
+ _labels = new ObservableCollectionExtended<string>();
+ var allLabels = WalletHelpers.GetLabels();
+
+ var mostUsedLabels = allLabels.GroupBy(x => x)
+ .Select(x => new
+ {
+ Label = x.Key,
+ Count = x.Count()
+ })
+ .OrderByDescending(x => x.Count)
+ .ToList();
+
+ _suggestions = mostUsedLabels.Select(x => x.Label).ToHashSet();
+
+ _suggestionLabels = new SourceList<SuggestionLabelViewModel>();
+ _suggestionLabelResults = new ObservableCollectionExtended<SuggestionLabelViewModel>();
+
+ _suggestionLabels.AddRange(
+ mostUsedLabels.Select(x => new SuggestionLabelViewModel(x.Label, x.Count, label => _addTag?.Invoke(label))));
+
+ var suggestionLabelsFilter = this.WhenAnyValue(x => x.Labels).Select(_ => Unit.Default)
+ .Merge(Observable.FromEventPattern(Labels, nameof(Labels.CollectionChanged)).Select(_ => Unit.Default))
+ .Select(SuggestionLabelsFilter);
+
+ _suggestionLabels
+ .Connect()
+ .Filter(suggestionLabelsFilter)
+ .Sort(SortExpressionComparer<SuggestionLabelViewModel>.Descending(x => x.Count))
+ .Top(3)
+ .ObserveOn(RxApp.MainThreadScheduler)
+ .Bind(_suggestionLabelResults)
+ .Subscribe();
+
+ SetAddTag = (addTag) => _addTag = addTag;
+ }
+
+ public ObservableCollection<SuggestionLabelViewModel> SuggestionLabelResults => _suggestionLabelResults;
+
+ public ObservableCollection<string> Labels => _labels;
+
+ public Action<Action<string>>? SetAddTag { get; }
+
+ private Func<SuggestionLabelViewModel, bool> SuggestionLabelsFilter(Unit unit)
+ {
+ return suggestionLabel => !_labels.Contains(suggestionLabel.Label);
+ }
+ }
+}
\ No newline at end of file

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Receive/ReceiveViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Receive/ReceiveViewModel.cs
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
using System.Linq;
-using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Windows.Input;
-using DynamicData;
using DynamicData.Binding;
using ReactiveUI;
using WalletWasabi.Blockchain.Analysis.Clustering;
using WalletWasabi.Blockchain.Keys;
-using WalletWasabi.Fluent.Helpers;
using WalletWasabi.Fluent.ViewModels.Navigation;
+using WalletWasabi.Fluent.ViewModels.Wallets.Labels;
using WalletWasabi.Wallets;
namespace WalletWasabi.Fluent.ViewModels.Wallets.Receive
@@ -27,71 +22,28 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Receive
public partial class ReceiveViewModel : RoutableViewModel
{
private readonly Wallet _wallet;
- private readonly SourceList<SuggestionLabelViewModel> _suggestionLabels;
- private readonly ObservableCollectionExtended<SuggestionLabelViewModel> _suggestionLabelResults;
- private readonly ObservableCollectionExtended<string> _labels;
- [AutoNotify] private HashSet<string> _suggestions;
[AutoNotify] private bool _isExistingAddressesButtonVisible;
+ [AutoNotify(SetterModifier = AccessModifier.Private)] private SuggestionLabelsViewModel _suggestionLabels;
public ReceiveViewModel(Wallet wallet)
{
_wallet = wallet;
- _labels = new ObservableCollectionExtended<string>();
- var allLabels = WalletHelpers.GetLabels();
-
- var mostUsedLabels = allLabels.GroupBy(x => x)
- .Select(x => new
- {
- Label = x.Key,
- Count = x.Count()
- })
- .OrderByDescending(x => x.Count)
- .ToList();
-
- _suggestions = mostUsedLabels.Select(x => x.Label).ToHashSet();
-
- _suggestionLabels = new SourceList<SuggestionLabelViewModel>();
- _suggestionLabelResults = new ObservableCollectionExtended<SuggestionLabelViewModel>();
-
- _suggestionLabels.AddRange(
- mostUsedLabels.Select(x => new SuggestionLabelViewModel(x.Label, x.Count)));
-
- var suggestionLabelsFilter = this.WhenAnyValue(x => x.Labels).Select(_ => Unit.Default)
- .Merge(Observable.FromEventPattern(Labels, nameof(Labels.CollectionChanged)).Select(_ => Unit.Default))
- .Select(SuggestionLabelsFilter);
-
- _suggestionLabels
- .Connect()
- .Filter(suggestionLabelsFilter)
- .Sort(SortExpressionComparer<SuggestionLabelViewModel>.Descending(x => x.Count))
- .Top(3)
- .ObserveOn(RxApp.MainThreadScheduler)
- .Bind(_suggestionLabelResults)
- .Subscribe();
-
SetupCancel(enableCancel: true, enableCancelOnEscape: true, enableCancelOnPressed: true);
EnableBack = false;
- NextCommand = ReactiveCommand.Create(OnNext, _labels.ToObservableChangeSet().Select(_ => _labels.Count > 0));
+ _suggestionLabels = new SuggestionLabelsViewModel();
+
+ NextCommand = ReactiveCommand.Create(OnNext, _suggestionLabels.Labels.ToObservableChangeSet().Select(_ => _suggestionLabels.Labels.Count > 0));
ShowExistingAddressesCommand = ReactiveCommand.Create(OnShowExistingAddresses);
}
public ICommand ShowExistingAddressesCommand { get; }
- public ObservableCollection<SuggestionLabelViewModel> SuggestionLabelResults => _suggestionLabelResults;
-
- public ObservableCollection<string> Labels => _labels;
-
- private Func<SuggestionLabelViewModel, bool> SuggestionLabelsFilter(Unit unit)
- {
- return suggestionLabel => !_labels.Contains(suggestionLabel.Label);
- }
-
private void OnNext()
{
- var newKey = _wallet.KeyManager.GetNextReceiveKey(new SmartLabel(Labels), out bool minGapLimitIncreased);
+ var newKey = _wallet.KeyManager.GetNextReceiveKey(new SmartLabel(SuggestionLabels.Labels), out bool minGapLimitIncreased);
if (minGapLimitIncreased)
{
@@ -102,14 +54,14 @@ namespace WalletWasabi.Fluent.ViewModels.Wallets.Receive
// TODO: notification
}
- Labels.Clear();
+ _suggestionLabels.Labels.Clear();
Navigate().To(new ReceiveAddressViewModel(_wallet, newKey));
}
private void OnShowExistingAddresses()
{
- Navigate().To(new ReceiveAddressesViewModel(_wallet, Suggestions));
+ Navigate().To(new ReceiveAddressesViewModel(_wallet, _suggestionLabels.Suggestions));
}
protected override void OnNavigatedTo(bool isInHistory, CompositeDisposable disposable)

DELETE
WalletWasabi.Fluent/ViewModels/Wallets/Receive/SuggestionLabelViewModel.cs
-namespace WalletWasabi.Fluent.ViewModels.Wallets.Receive
-{
- public partial class SuggestionLabelViewModel : ViewModelBase
- {
- public SuggestionLabelViewModel(string label, int count)
- {
- Label = label;
- Count = count;
- }
-
- public string Label { get; }
-
- public int Count { get; }
- }
-}
\ No newline at end of file

ADD
WalletWasabi.Fluent/Views/Wallets/Labels/SuggestionLabelsView.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"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ xmlns:labels="clr-namespace:WalletWasabi.Fluent.ViewModels.Wallets.Labels"
+ x:DataType="labels:SuggestionLabelsViewModel"
+ x:CompileBindings="True"
+ x:Class="WalletWasabi.Fluent.Views.Wallets.Labels.SuggestionLabelsView">
+ <ItemsControl Items="{Binding SuggestionLabelResults}"
+ ClipToBounds="False">
+ <ItemsControl.ItemsPanel>
+ <ItemsPanelTemplate>
+ <StackPanel Orientation="Horizontal" Spacing="2" />
+ </ItemsPanelTemplate>
+ </ItemsControl.ItemsPanel>
+ <ItemsControl.ItemTemplate>
+ <DataTemplate x:DataType="labels:SuggestionLabelViewModel">
+ <Button Classes="activeHyperLink"
+ Margin="0,0,4,0"
+ Focusable="False"
+ Content="{Binding Label}"
+ Command="{Binding AddTagCommand}"
+ CommandParameter="{Binding Label}"
+ x:CompileBindings="False"/>
+ </DataTemplate>
+ </ItemsControl.ItemTemplate>
+ </ItemsControl>
+</UserControl>
\ No newline at end of file

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

MODIFY
WalletWasabi.Fluent/Views/Wallets/Receive/ReceiveView.axaml
WalletWasabi.Fluent/Views/Wallets/Receive/ReceiveView.axaml
xmlns:behaviors="clr-namespace:WalletWasabi.Fluent.Behaviors"
xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
xmlns:receive="clr-namespace:WalletWasabi.Fluent.ViewModels.Wallets.Receive"
+ xmlns:labels="clr-namespace:WalletWasabi.Fluent.Views.Wallets.Labels"
x:DataType="receive:ReceiveViewModel"
x:CompileBindings="True"
x:Class="WalletWasabi.Fluent.Views.Wallets.Receive.ReceiveView">
<TextBlock TextWrapping="Wrap" Margin="0 0 10 0" Text="Who will know this receive address belongs to you?" HorizontalAlignment="Left"
FontWeight="Normal" />
</DockPanel>
- <c:TagsBox x:Name="LabelsTbx" Items="{Binding Labels}"
+ <c:TagsBox x:Name="LabelsTbx"
+ DataContext="{Binding SuggestionLabels}"
+ SetAddTagAction="{Binding SetAddTag}"
+ Items="{Binding Labels}"
TagSeparator=","
SuggestionsAreCaseSensitive="True"
RestrictInputToSuggestions="False"
<behaviors:FocusOnAttachedBehavior />
</i:Interaction.Behaviors>
</c:TagsBox>
- <ItemsControl Items="{Binding SuggestionLabelResults}"
- ClipToBounds="False">
- <ItemsControl.ItemsPanel>
- <ItemsPanelTemplate>
- <StackPanel Orientation="Horizontal" Spacing="2" />
- </ItemsPanelTemplate>
- </ItemsControl.ItemsPanel>
- <ItemsControl.ItemTemplate>
- <DataTemplate x:DataType="receive:SuggestionLabelViewModel">
- <Button Classes="option"
- Margin="0,0,4,0"
- Focusable="False"
- Content="{Binding Label}"
- Command="{Binding #LabelsTbx.AddTag}"
- CommandParameter="{Binding Label}"
- x:CompileBindings="False"/>
- </DataTemplate>
- </ItemsControl.ItemTemplate>
- </ItemsControl>
+ <labels:SuggestionLabelsView DataContext="{Binding SuggestionLabels}"/>
</StackPanel>
</DockPanel>
</c:ContentArea>

Refactor suggested labels to separate view