Refactor SearchSources
Filters are now responsability of each source
MODIFY
WalletWasabi.Fluent/ViewModels/MainViewModel.cs
WalletWasabi.Fluent/ViewModels/MainViewModel.cs
-using System.Collections.Generic;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
+using System.Reactive.Subjects;
using Avalonia.Controls;
-using DynamicData;
using NBitcoin;
using ReactiveUI;
using WalletWasabi.Fluent.AppServices.Tor;
@@ -14,7 +13,6 @@ using WalletWasabi.Fluent.ViewModels.NavBar;
using WalletWasabi.Fluent.ViewModels.Navigation;
using WalletWasabi.Fluent.ViewModels.OpenDirectory;
using WalletWasabi.Fluent.ViewModels.SearchBar;
-using WalletWasabi.Fluent.ViewModels.SearchBar.SearchItems;
using WalletWasabi.Fluent.ViewModels.SearchBar.Sources;
using WalletWasabi.Fluent.ViewModels.Settings;
using WalletWasabi.Fluent.ViewModels.StatusIcon;
@@ -108,8 +106,10 @@ public partial class MainViewModel : ViewModelBase
}
});
- var source = new CompositeSearchItemsSource(new ActionsSource(), new SettingsSource(_settingsPage), new TransactionsSource());
+ var subject = new Subject<string>();
+ var source = new CompositeSearchSource(new ActionsSource(subject), new SettingsSearchSource(_settingsPage, subject), new TransactionsSource(subject));
SearchBar = new SearchBarViewModel(source.Changes);
+ SearchBar.WhenAnyValue(a => a.SearchText).WhereNotNull().Subscribe(subject);
NetworkBadgeName = Services.Config.Network == Network.Main ? "" : Services.Config.Network.Name;
}

MODIFY
WalletWasabi.Fluent/ViewModels/SearchBar/SearchBarViewModel.cs
WalletWasabi.Fluent/ViewModels/SearchBar/SearchBarViewModel.cs
@@ -17,17 +17,7 @@ public partial class SearchBarViewModel : ReactiveObject
public SearchBarViewModel(IObservable<IChangeSet<ISearchItem, ComposedKey>> itemsObservable)
{
- var filterPredicate = this
- .WhenAnyValue(x => x.SearchText)
- .Throttle(TimeSpan.FromMilliseconds(250), RxApp.MainThreadScheduler)
- .DistinctUntilChanged()
- .Select(SearchItemFilterFunc);
-
- var filteredItems = itemsObservable
- .RefCount()
- .Filter(filterPredicate);
-
- filteredItems
+ itemsObservable
.Group(s => s.Category)
.Transform(group => new SearchItemGroup(group.Key, group.Cache))
.Bind(out _groups)
@@ -35,7 +25,7 @@ public partial class SearchBarViewModel : ReactiveObject
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe();
- HasResults = filteredItems
+ HasResults = itemsObservable
.Count()
.Select(i => i > 0)
.Replay(1)
@@ -45,21 +35,4 @@ public partial class SearchBarViewModel : ReactiveObject
public IObservable<bool> HasResults { get; }
public ReadOnlyObservableCollection<SearchItemGroup> Groups => _groups;
-
- private static Func<ISearchItem, bool> SearchItemFilterFunc(string? text)
- {
- return searchItem =>
- {
- if (string.IsNullOrWhiteSpace(text))
- {
- return searchItem.IsDefault;
- }
-
- var containsName = searchItem.Name.Contains(text, StringComparison.InvariantCultureIgnoreCase);
- var containsCategory = searchItem.Category.Contains(text, StringComparison.InvariantCultureIgnoreCase);
- var containsAnyTag =
- searchItem.Keywords.Any(s => s.Contains(text, StringComparison.InvariantCultureIgnoreCase));
- return containsName || containsCategory || containsAnyTag;
- };
- }
}

RENAME
WalletWasabi.Fluent/ViewModels/SearchBar/Sources/SettingsSource.cs
WalletWasabi.Fluent/ViewModels/SearchBar/SettingsSearchSource.cs
using System.Collections.Generic;
+using System.Linq;
using System.Reactive.Linq;
using DynamicData;
using WalletWasabi.Fluent.ViewModels.SearchBar.Patterns;
using WalletWasabi.Fluent.ViewModels.SearchBar.SearchItems;
using WalletWasabi.Fluent.ViewModels.SearchBar.Settings;
+using WalletWasabi.Fluent.ViewModels.SearchBar.Sources;
using WalletWasabi.Fluent.ViewModels.Settings;
-namespace WalletWasabi.Fluent.ViewModels.SearchBar.Sources;
+namespace WalletWasabi.Fluent.ViewModels.SearchBar;
-public class SettingsSource : ISearchItemSource
+public class SettingsSearchSource : ISearchSource
{
private readonly SettingsPageViewModel _settingsPage;
- public SettingsSource(SettingsPageViewModel settingsPage)
+ public SettingsSearchSource(SettingsPageViewModel settingsPage, IObservable<string> query)
{
_settingsPage = settingsPage;
- }
- public IObservable<IChangeSet<ISearchItem, ComposedKey>> Changes => GetSettingsItems()
+ var filter = query.Select(SearchSource.DefaultFilter);
+
+ Changes = GetSettingsItems()
.ToObservable()
- .ToObservableChangeSet(x => x.Key);
+ .ToObservableChangeSet(x => x.Key)
+ .Filter(filter);
+ }
+
+ public IObservable<IChangeSet<ISearchItem, ComposedKey>> Changes { get; }
private IEnumerable<ISearchItem> GetSettingsItems()
{

MODIFY
WalletWasabi.Fluent/ViewModels/SearchBar/Sources/ActionsSource.cs
WalletWasabi.Fluent/ViewModels/SearchBar/Sources/ActionsSource.cs
@@ -10,11 +10,36 @@ using WalletWasabi.Fluent.ViewModels.SearchBar.SearchItems;
namespace WalletWasabi.Fluent.ViewModels.SearchBar.Sources;
-public class ActionsSource : ISearchItemSource
+public static class SearchSource
{
- public IObservable<IChangeSet<ISearchItem, ComposedKey>> Changes => GetItemsFromMetadata()
+ public static Func<ISearchItem, bool> DefaultFilter(string query)
+ {
+ return item =>
+ {
+ if (string.IsNullOrWhiteSpace(query))
+ {
+ return item.IsDefault;
+ }
+
+ return new[] { item.Name, item.Description, }.Concat(item.Keywords)
+ .Any(s => s.Contains(query, StringComparison.InvariantCultureIgnoreCase));
+ };
+ }
+}
+
+public class ActionsSource : ISearchSource
+{
+ public ActionsSource(IObservable<string> query)
+ {
+ var filter = query.Select(SearchSource.DefaultFilter);
+
+ Changes = GetItemsFromMetadata()
.ToObservable()
- .ToObservableChangeSet(x => x.Key);
+ .ToObservableChangeSet(x => x.Key)
+ .Filter(filter);
+ }
+
+ public IObservable<IChangeSet<ISearchItem, ComposedKey>> Changes { get; }
private static IEnumerable<ISearchItem> GetItemsFromMetadata()
{

RENAME
WalletWasabi.Fluent/ViewModels/SearchBar/Sources/CompositeSearchItemsSource.cs
WalletWasabi.Fluent/ViewModels/SearchBar/Sources/CompositeSearchSource.cs
@@ -6,11 +6,11 @@ using WalletWasabi.Fluent.ViewModels.SearchBar.SearchItems;
namespace WalletWasabi.Fluent.ViewModels.SearchBar.Sources;
-public class CompositeSearchItemsSource : ISearchItemSource
+public class CompositeSearchSource : ISearchSource
{
- private readonly ISearchItemSource[] _sources;
+ private readonly ISearchSource[] _sources;
- public CompositeSearchItemsSource(params ISearchItemSource[] sources)
+ public CompositeSearchSource(params ISearchSource[] sources)
{
_sources = sources;
}

RENAME
WalletWasabi.Fluent/ViewModels/SearchBar/Sources/ISearchItemSource.cs
WalletWasabi.Fluent/ViewModels/SearchBar/Sources/ISearchSource.cs
@@ -4,7 +4,7 @@ using WalletWasabi.Fluent.ViewModels.SearchBar.SearchItems;
namespace WalletWasabi.Fluent.ViewModels.SearchBar.Sources;
-public interface ISearchItemSource
+public interface ISearchSource
{
IObservable<IChangeSet<ISearchItem, ComposedKey>> Changes { get; }
}

MODIFY
WalletWasabi.Fluent/ViewModels/SearchBar/Sources/TransactionItemsSource.cs
WalletWasabi.Fluent/ViewModels/SearchBar/Sources/TransactionItemsSource.cs
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reactive.Linq;
using System.Threading.Tasks;
using DynamicData;
+using ReactiveUI;
using WalletWasabi.Fluent.ViewModels.SearchBar.Patterns;
using WalletWasabi.Fluent.ViewModels.SearchBar.SearchItems;
-using WalletWasabi.Fluent.ViewModels.Wallets.Home.History;
+using WalletWasabi.Fluent.ViewModels.Wallets;
using WalletWasabi.Fluent.ViewModels.Wallets.Home.History.HistoryItems;
+using WalletWasabi.Wallets;
namespace WalletWasabi.Fluent.ViewModels.SearchBar.Sources;
-public class TransactionsSource : ISearchItemSource
+public class TransactionsSource : ReactiveObject, ISearchSource
{
- public IObservable<IChangeSet<ISearchItem, ComposedKey>> Changes =>
- TransactionsWatcher.Instance.TransactionChanges
- .Transform(ToSearchItem);
+ public TransactionsSource(IObservable<string> query)
+ {
+ var sourceCache = new SourceCache<ISearchItem, ComposedKey>(x => x.Key);
+
+ query
+ .Select(s => s.Length > 5 ? PerformSearch(s) : Enumerable.Empty<ISearchItem>())
+ .Do(results => sourceCache.Edit(e => e.Load(results)))
+ .Subscribe();
+
+ Changes = sourceCache.Connect();
+ }
- private static ISearchItem ToSearchItem(TransactionEntry r)
+ public IObservable<IChangeSet<ISearchItem, ComposedKey>> Changes { get; }
+
+ private static bool Contains(string queryStr, HistoryItemViewModelBase historyItemViewModelBase)
{
- var transactionId = r.HistoryItem.Id.ToString();
- var keywords = new[] { r.HistoryItem.Id.ToString() };
- return new ActionableItem(
- transactionId,
- $"Found in {r.Wallet.WalletName}",
- () =>
+ return historyItemViewModelBase.Id.ToString().Contains(queryStr, StringComparison.CurrentCultureIgnoreCase);
+ }
+
+ private static Task NavigateTo(WalletViewModel wallet, HistoryItemViewModelBase item)
{
- r.Wallet.NavigateAndHighlight(r.HistoryItem.Id);
+ wallet.NavigateAndHighlight(item.Id);
return Task.CompletedTask;
- },
- "Transactions",
- keywords)
+ }
+
+ private static string GetIcon(HistoryItemViewModelBase historyItemViewModelBase)
{
- Icon = GetIcon(r)
+ return historyItemViewModelBase switch
+ {
+ CoinJoinHistoryItemViewModel => "shield_regular",
+ CoinJoinsHistoryItemViewModel => "shield_regular",
+ TransactionHistoryItemViewModel => "normal_transaction",
+ _ => ""
};
}
- private static string GetIcon(TransactionEntry transactionEntry)
+ private static IEnumerable<(WalletViewModel, HistoryItemViewModelBase)> Flatten(
+ IEnumerable<(WalletViewModel Wallet, IEnumerable<HistoryItemViewModelBase> Transactions)>
+ walletTransactions)
+ {
+ return walletTransactions.SelectMany(t => t.Transactions.Select(tran => (t.Wallet, tran)));
+ }
+
+ private static ISearchItem ToSearchItem(WalletViewModel wallet, HistoryItemViewModelBase item)
{
- return transactionEntry.HistoryItem switch
+ return new ActionableItem(
+ item.Id.ToString(),
+ @$"Found in ""{wallet.WalletName}""",
+ () => NavigateTo(wallet, item),
+ "Transactions",
+ new List<string>())
{
- CoinJoinHistoryItemViewModel cj => "shield_regular",
- CoinJoinsHistoryItemViewModel cjs => "shield_regular",
- TransactionHistoryItemViewModel tx => "normal_transaction",
- _ => ""
+ Icon = GetIcon(item)
};
}
+
+ private static IEnumerable<(WalletViewModel Wallet, IEnumerable<HistoryItemViewModelBase> Transactions)>
+ GetTransactionsByWallet()
+ {
+ return UiServices.WalletManager.Wallets
+ .Where(x => x.IsLoggedIn && x.WalletState == WalletState.Started)
+ .OfType<WalletViewModel>()
+ .Select(x => (Wallet: x, x.History.Transactions.Concat(x.History.Transactions.OfType<CoinJoinsHistoryItemViewModel>().SelectMany(y => y.Children))));
+ }
+
+ private IEnumerable<ISearchItem> PerformSearch(string s)
+ {
+ return Filter(s)
+ .Take(5)
+ .Select(tuple => ToSearchItem(tuple.Item1, tuple.Item2));
+ }
+
+ private IEnumerable<(WalletViewModel, HistoryItemViewModelBase)> Filter(string queryStr)
+ {
+ return Flatten(GetTransactionsByWallet())
+ .Where(tuple => Contains(queryStr, tuple.Item2));
+ }
}

Refactor SearchSources

Filters are now responsability of each source