Transactions now appear in the SearchBar
MODIFY
WalletWasabi.Fluent/App.axaml.cs
WalletWasabi.Fluent/App.axaml.cs
@@ -33,6 +33,8 @@ public class App : Application
DataContext = applicationViewModel;
applicationViewModel.ShowRequested += (sender, args) => ShowRequested?.Invoke(sender, args);
}
+
+ TransactionWatcher.Instance = new TransactionWatcher();
}
public App(Func<Task> backendInitialiseAsync) : this()

ADD
WalletWasabi.Fluent/TransactionWatcher.cs
+using System.Linq;
+using DynamicData;
+using ReactiveUI;
+using WalletWasabi.Fluent.ViewModels.SearchBar;
+using WalletWasabi.Fluent.ViewModels.Wallets.Home.History;
+
+namespace WalletWasabi.Fluent;
+
+public class TransactionWatcher
+{
+ public static TransactionWatcher Instance { get; set; } = new();
+
+ public SourceCache<TransactionEntry, ComposedKey> TransactionCache { get; }
+
+ public TransactionWatcher()
+ {
+ TransactionCache = new SourceCache<TransactionEntry, ComposedKey>(x => x.Key);
+ MessageBus.Current.Listen<TransactionsChangedMessage>()
+ .Subscribe(msg =>
+ {
+ TransactionCache.Edit(r =>
+ {
+ var toRemove = r.Items.Where(r => r.Parent == msg.HistoryViewModel).ToList();
+ r.Remove(toRemove);
+ r.AddOrUpdate(msg.NewHistoryList.Select(b => new TransactionEntry(msg.HistoryViewModel, b)));
+ });
+ });
+ }
+}
\ No newline at end of file

MODIFY
WalletWasabi.Fluent/ViewModels/MainViewModel.cs
WalletWasabi.Fluent/ViewModels/MainViewModel.cs
@@ -4,6 +4,7 @@ using System.Reactive.Concurrency;
using System.Reactive.Linq;
using Avalonia;
using Avalonia.Controls;
+using DynamicData;
using NBitcoin;
using ReactiveUI;
using WalletWasabi.Fluent.ViewModels.AddWallet;
@@ -174,12 +175,16 @@ public partial class MainViewModel : ViewModelBase
}
});
- _walletWatcher = new WalletWatcher(Services.WalletManager);
+ var transactionChanges = TransactionWatcher.Instance.TransactionCache.Connect()
+ .Transform(r => (ISearchItem)new NonActionableSearchItem(r.HistoryItem.Label, r.HistoryItem.Date.ToString(), "", new List<string>(), null)
+ {
+ IsDefault = true,
+ });
+
+ var staticItems = SearchItemProvider.GetSearchItems().ToObservableChangeSet(r => r.Key);
- var items = SearchItemProvider.GetSearchItems()
- .Merge(_walletWatcher.Transactions
- .Select(r => new NonActionableSearchItem(r.Amount.ToString(), r.Label, "Transactions", new List<string>(), null ){ IsDefault = true}));
- SearchBar = new SearchBarViewModel(items);
+ SearchBar = new
+ SearchBarViewModel(transactionChanges.Merge(staticItems));
}
public TargettedNavigationStack MainScreen { get; }

MODIFY
WalletWasabi.Fluent/ViewModels/SearchBar/SearchBarViewModel.cs
WalletWasabi.Fluent/ViewModels/SearchBar/SearchBarViewModel.cs
using System.Collections.ObjectModel;
using System.Linq;
+using System.Reactive;
using System.Reactive.Linq;
-using System.Windows.Input;
using DynamicData;
using ReactiveUI;
namespace WalletWasabi.Fluent.ViewModels.SearchBar;
-public partial class SearchBarViewModel : ReactiveObject
+public class SearchBarViewModel : ReactiveObject
{
private readonly ReadOnlyObservableCollection<SearchItemGroup> _groups;
+ private bool _isSearchListVisible;
+ private string _searchText;
- [AutoNotify] private bool _isSearchListVisible;
- [AutoNotify] private string _searchText = "";
-
- public SearchBarViewModel(IObservable<ISearchItem> itemsObservable)
+ public SearchBarViewModel(IObservable<IChangeSet<ISearchItem, ComposedKey>> itemsObservable)
{
- var vms = itemsObservable.Select(item =>
- item is ActionableItem i ? new AutocloseActionableItem(i, () => IsSearchListVisible = false) : item);
-
- var source = new SourceCache<ISearchItem, ComposedKey>(item => item.Key);
- source.PopulateFrom(vms);
-
var filterPredicate = this
.WhenAnyValue(x => x.SearchText)
.Throttle(TimeSpan.FromMilliseconds(250), RxApp.TaskpoolScheduler)
.DistinctUntilChanged()
.Select(SearchItemFilterFunc);
- source.Connect()
+ itemsObservable
.RefCount()
.Filter(filterPredicate)
.Group(s => s.Category)
@@ -39,16 +32,24 @@ public partial class SearchBarViewModel : ReactiveObject
.Subscribe();
ShowListCommand = ReactiveCommand.Create(() => IsSearchListVisible = true);
-
- //this.WhenAnyValue(x => x.IsSearchListVisible)
- // .Where(x => x == false)
- // .Subscribe(_ => SearchText = "");
}
- public ICommand ShowListCommand { get; }
+ public ReactiveCommand<Unit, bool> ShowListCommand { get; set; }
+
+ public bool IsSearchListVisible
+ {
+ get => _isSearchListVisible;
+ set => this.RaiseAndSetIfChanged(ref _isSearchListVisible, value);
+ }
public ReadOnlyObservableCollection<SearchItemGroup> Groups => _groups;
+ public string SearchText
+ {
+ get => _searchText;
+ set => this.RaiseAndSetIfChanged(ref _searchText, value);
+ }
+
private static Func<ISearchItem, bool> SearchItemFilterFunc(string? text)
{
return searchItem =>

MODIFY
WalletWasabi.Fluent/ViewModels/SearchBar/SearchItemProvider.cs
WalletWasabi.Fluent/ViewModels/SearchBar/SearchItemProvider.cs
@@ -2,8 +2,9 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
-using WalletWasabi.Fluent.ViewModels.NavBar;
+using ReactiveUI;
using WalletWasabi.Fluent.ViewModels.Navigation;
+using WalletWasabi.Fluent.ViewModels.Wallets.Home.History;
namespace WalletWasabi.Fluent.ViewModels.SearchBar;
@@ -12,24 +13,24 @@ public static class SearchItemProvider
public static IObservable<ISearchItem> GetSearchItems()
{
return GetItemsFromMetadata()
- .Concat(GetAdditionalItems())
+ .Concat(GetSettingsItems())
.ToObservable();
}
- private static IEnumerable<ISearchItem> GetAdditionalItems()
+ private static IEnumerable<ISearchItem> GetSettingsItems()
{
return new ISearchItem[]
{
- new NonActionableSearchItem(new DarkThemeSetting(Services.UiConfig), "Dark theme", "Appearance", new List<string>(), null){ IsDefault = false},
- new NonActionableSearchItem(new Setting<UiConfig, bool>(Services.UiConfig, b => b.Autocopy), "Autocopy", "Settings", new List<string>(), null) { IsDefault = false },
- new NonActionableSearchItem(new Setting<UiConfig, bool>(Services.UiConfig, b => b.AutoPaste), "Autopaste", "Settings", new List<string>(), null) { IsDefault = false },
- new NonActionableSearchItem(new Setting<UiConfig, bool>(Services.UiConfig, b => b.HideOnClose), "Hide on close", "Settings", new List<string>(), null) { IsDefault = false },
- new NonActionableSearchItem(new Setting<UiConfig, bool>(Services.UiConfig, b => b.RunOnSystemStartup), "Run on system startup", "Settings", new List<string>(), null) { IsDefault = false },
- new NonActionableSearchItem(new Setting<Config, bool>(Services.Config, b => b.UseTor), "User Tor", "Settings", new List<string>(), null) { IsDefault = false },
- new NonActionableSearchItem(new Setting<Config, bool>(Services.Config, b => b.TerminateTorOnExit), "Terminate Tor on exit", "Settings", new List<string>(), null) { IsDefault = false },
- new NonActionableSearchItem(new Setting<Config, bool>(Services.Config, b => b.StartLocalBitcoinCoreOnStartup), "Start local Bitcoin core on startup", "Settings", new List<string>(), null) { IsDefault = false },
- new NonActionableSearchItem(new Setting<Config, bool>(Services.Config, b => b.StopLocalBitcoinCoreOnShutdown), "Start local Bitcoin core on shutdown", "Settings", new List<string>(), null) { IsDefault = false },
- new NonActionableSearchItem(new Setting<Config, bool>(Services.Config, b => b.JsonRpcServerEnabled), "Enable JSON-RPC Server", "Settings", new List<string>(), null) { IsDefault = false },
+ new NonActionableSearchItem(new DarkThemeSetting(Services.UiConfig), "Dark theme", "Appearance", new List<string>(), "nav_settings_regular"){ IsDefault = false},
+ new NonActionableSearchItem(new Setting<UiConfig, bool>(Services.UiConfig, b => b.Autocopy), "Autocopy Bitcoin address", "Settings", new List<string>(), "nav_settings_regular") { IsDefault = false },
+ new NonActionableSearchItem(new Setting<UiConfig, bool>(Services.UiConfig, b => b.AutoPaste), "Autopaste Bitcoin address", "Settings", new List<string>(), "nav_settings_regular") { IsDefault = false },
+ new NonActionableSearchItem(new Setting<UiConfig, bool>(Services.UiConfig, b => b.HideOnClose), "Hide on close", "Settings", new List<string>(), "nav_settings_regular") { IsDefault = false },
+ new NonActionableSearchItem(new Setting<UiConfig, bool>(Services.UiConfig, b => b.RunOnSystemStartup), "Run on system startup", "Settings", new List<string>(), "nav_settings_regular") { IsDefault = false },
+ new NonActionableSearchItem(new Setting<Config, bool>(Services.Config, b => b.UseTor), "User Tor", "Settings", new List<string>(), "nav_settings_regular") { IsDefault = false },
+ new NonActionableSearchItem(new Setting<Config, bool>(Services.Config, b => b.TerminateTorOnExit), "Terminate Tor on exit", "Settings", new List<string>(), "nav_settings_regular") { IsDefault = false },
+ new NonActionableSearchItem(new Setting<Config, bool>(Services.Config, b => b.StartLocalBitcoinCoreOnStartup), "Start local Bitcoin core on startup", "Settings", new List<string>(), "nav_settings_regular") { IsDefault = false },
+ new NonActionableSearchItem(new Setting<Config, bool>(Services.Config, b => b.StopLocalBitcoinCoreOnShutdown), "Start local Bitcoin core on shutdown", "Settings", new List<string>(), "nav_settings_regular") { IsDefault = false },
+ new NonActionableSearchItem(new Setting<Config, bool>(Services.Config, b => b.JsonRpcServerEnabled), "Enable JSON-RPC Server", "Settings", new List<string>(), "nav_settings_regular") { IsDefault = false },
};
}
@@ -39,8 +40,8 @@ public static class SearchItemProvider
.Where(m => m.Searchable)
.Select(m =>
{
- var onActivate = CreateOnActivateFunction(m);
- var searchItem = new ActionableItem(m.Title, m.Caption, onActivate, m.Category ?? "No category", m.Keywords)
+ var func = CreateFunc(m);
+ var searchItem = new ActionableItem(m.Title, m.Caption, func, m.Category ?? "No category", m.Keywords)
{
Icon = m.IconName,
IsDefault = true,
@@ -49,7 +50,7 @@ public static class SearchItemProvider
});
}
- private static Func<Task> CreateOnActivateFunction(NavigationMetaData navigationMetaData)
+ private static Func<Task> CreateFunc(NavigationMetaData navigationMetaData)
{
return async () =>
{
@@ -59,14 +60,36 @@ public static class SearchItemProvider
return;
}
- if (vm is NavBarItemViewModel item && item.OpenCommand.CanExecute(default))
+ Navigate(vm.DefaultTarget).To(vm);
+ };
+ }
+
+ private static INavigationStack<RoutableViewModel> Navigate(NavigationTarget currentTarget)
+ {
+ return currentTarget switch
{
- item.OpenCommand.Execute(default);
+ NavigationTarget.HomeScreen => NavigationState.Instance.HomeScreenNavigation,
+ NavigationTarget.DialogScreen => NavigationState.Instance.DialogScreenNavigation,
+ NavigationTarget.FullScreen => NavigationState.Instance.FullScreenNavigation,
+ NavigationTarget.CompactDialogScreen => NavigationState.Instance.CompactDialogScreenNavigation,
+ _ => throw new NotSupportedException()
+ };
+ }
}
- else
+
+public class TransactionObserver
{
- RoutableViewModel.Navigate(vm.DefaultTarget).To(vm);
+ public TransactionObserver()
+ {
+ var transactions = MessageBus.Current.Listen<TransactionsChangedMessage>()
+ .Select(m => new TransactionSearchItem(m));
}
- };
+}
+
+public class TransactionSearchItem
+{
+ public TransactionSearchItem(TransactionsChangedMessage transactionsChangedMessage)
+ {
+ throw new NotImplementedException();
}
}
\ No newline at end of file

MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Home/History/HistoryViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Home/History/HistoryViewModel.cs
+using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
@@ -39,6 +40,8 @@ public partial class HistoryViewModel : ActivatableViewModel
[AutoNotify(SetterModifier = AccessModifier.Private)]
private bool _isInitialized;
+ private readonly SourceCache<HistoryItemViewModelBase, uint256> _sourceCache;
+
public HistoryViewModel(WalletViewModel walletViewModel, IObservable<Unit> updateTrigger)
{
_walletViewModel = walletViewModel;
@@ -227,6 +230,7 @@ public partial class HistoryViewModel : ActivatableViewModel
var historyBuilder = new TransactionHistoryBuilder(_walletViewModel.Wallet);
var rawHistoryList = await Task.Run(historyBuilder.BuildHistorySummary);
var newHistoryList = GenerateHistoryList(rawHistoryList).ToArray();
+ MessageBus.Current.SendMessage(new TransactionsChangedMessage(this, newHistoryList));
_transactionSourceList.Edit(x =>
{
@@ -234,6 +238,8 @@ public partial class HistoryViewModel : ActivatableViewModel
x.AddRange(newHistoryList);
});
+
+
if (!IsInitialized)
{
IsInitialized = true;

ADD
WalletWasabi.Fluent/ViewModels/Wallets/Home/History/TransactionEntry.cs
+using WalletWasabi.Fluent.ViewModels.SearchBar;
+using WalletWasabi.Fluent.ViewModels.Wallets.Home.History.HistoryItems;
+
+namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.History;
+
+public class TransactionEntry
+{
+ public HistoryItemViewModelBase HistoryItem { get; }
+
+ public TransactionEntry(HistoryViewModel parent, HistoryItemViewModelBase historyItem)
+ {
+ Parent = parent;
+ HistoryItem = historyItem;
+ Key = new ComposedKey(Parent, HistoryItem.Id);
+ }
+
+ public ComposedKey Key { get; }
+ public HistoryViewModel Parent { get; set; }
+}
\ No newline at end of file

ADD
WalletWasabi.Fluent/ViewModels/Wallets/Home/History/TransactionsChangedMessage.cs
+using WalletWasabi.Fluent.ViewModels.Wallets.Home.History.HistoryItems;
+
+namespace WalletWasabi.Fluent.ViewModels.Wallets.Home.History;
+
+public class TransactionsChangedMessage
+{
+ public HistoryViewModel HistoryViewModel { get; }
+ public HistoryItemViewModelBase[] NewHistoryList { get; }
+
+ public TransactionsChangedMessage(HistoryViewModel historyViewModel, HistoryItemViewModelBase[] newHistoryList)
+ {
+ HistoryViewModel = historyViewModel;
+ NewHistoryList = newHistoryList;
+ }
+}
\ No newline at end of file

Transactions now appear in the SearchBar