Enable loading of multiple wallets.
ADD
WalletWasabi.Gui/Controls/WalletExplorer/ClosedWalletViewModel.cs
+using AvalonStudio.Extensibility;
+using ReactiveUI;
+using Splat;
+using System.IO;
+using System.Linq;
+using System.Reactive;
+using System.Threading;
+
+namespace WalletWasabi.Gui.Controls.WalletExplorer
+{
+ public class ClosedWalletViewModel : WalletViewModelBase
+ {
+ public ClosedWalletViewModel(string path) : base(Path.GetFileNameWithoutExtension(path))
+ {
+ OpenWalletCommand = ReactiveCommand.CreateFromTask(async () =>
+ {
+ IsBusy = true;
+
+ var global = Locator.Current.GetService<Global>();
+
+ if (!await global.WaitForInitializationCompletedAsync(CancellationToken.None))
+ {
+ return;
+ }
+
+ var walletManager = global.WalletManager;
+
+ var keyManager = global.LoadKeyManager(Title);
+
+ if (keyManager is null)
+ {
+ IsBusy = false;
+ return;
+ }
+
+ var walletService = await walletManager.CreateAndStartWalletAsync(keyManager);
+
+ var walletExplorer = IoC.Get<WalletExplorerViewModel>();
+
+ var select = walletExplorer.SelectedItem == this;
+
+ walletExplorer.RemoveWallet(this);
+
+ if (walletService.Coins.Any())
+ {
+ // If already have coins then open the last active tab first.
+ walletExplorer.OpenWallet(walletService, receiveDominant: false, select: select);
+ }
+ else // Else open with Receive tab first.
+ {
+ walletExplorer.OpenWallet(walletService, receiveDominant: true, select: select);
+ }
+
+ IsBusy = false;
+ });
+ }
+
+ public ReactiveCommand<Unit, Unit> OpenWalletCommand { get; }
+ }
+}

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/WalletExplorerView.xaml
WalletWasabi.Gui/Controls/WalletExplorer/WalletExplorerView.xaml
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModels="clr-namespace:WalletWasabi.Gui.Controls.WalletExplorer;assembly=WalletWasabi.Gui"
x:Class="WalletWasabi.Gui.Controls.WalletExplorer.WalletExplorerView">
+ <UserControl.Styles>
+ <Style Selector="DrawingPresenter#PART_Spinner">
+ <Style.Animations>
+ <Animation Duration="0:0:2.5" IterationCount="Infinite">
+ <KeyFrame Cue="100%">
+ <Setter Property="RotateTransform.Angle" Value="360" />
+ </KeyFrame>
+ </Animation>
+ </Style.Animations>
+ </Style>
+ </UserControl.Styles>
<Grid>
<TreeView BorderThickness="0" Items="{Binding Wallets}" SelectedItem="{Binding SelectedItem}">
<TreeView.Styles>
<Style Selector="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style>
- <Style Selector="TreeViewItem:selected /template/ Border#SelectionBorder">
- <Setter Property="Background" Value="Transparent" />
- </Style>
</TreeView.Styles>
<TreeView.DataTemplates>
+ <TreeDataTemplate DataType="ViewModels:ClosedWalletViewModel" ItemsSource="{Binding Actions}">
+ <StackPanel Orientation="Horizontal" Spacing="6">
+ <StackPanel.ContextMenu>
+ <ContextMenu>
+ <MenuItem Header="Open Wallet" Command="{Binding OpenWalletCommand}" />
+ </ContextMenu>
+ </StackPanel.ContextMenu>
+ <DrawingPresenter Width="20" Height="20" Drawing="{DynamicResource WalletExplorer_ClosedWallet}" />
+ <TextBlock Text="{Binding Title}" Height="19" VerticalAlignment="Center" />
+ <DrawingPresenter Name="PART_Spinner" Width="16" Height="16" IsVisible="{Binding IsBusy}">
+ <GeometryDrawing Brush="{DynamicResource ThemeAccentBrush}" Geometry="M304 48c0 26.51-21.49 48-48 48s-48-21.49-48-48 21.49-48 48-48 48 21.49 48 48zm-48 368c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48zm208-208c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48zM96 256c0-26.51-21.49-48-48-48S0 229.49 0 256s21.49 48 48 48 48-21.49 48-48zm12.922 99.078c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48c0-26.509-21.491-48-48-48zm294.156 0c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48c0-26.509-21.49-48-48-48zM108.922 60.922c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.491-48-48-48z" />
+ </DrawingPresenter>
+ </StackPanel>
+ </TreeDataTemplate>
<TreeDataTemplate DataType="ViewModels:WalletViewModel" ItemsSource="{Binding Actions}">
<StackPanel Orientation="Horizontal" Spacing="6">
- <DrawingPresenter Width="20" Height="20" Drawing="{DynamicResource WalletExplorerView_OpenFolder}" />
+ <DrawingPresenter Width="20" Height="20" Drawing="{DynamicResource WalletExplorer_OpenWallet}" />
<Button Content="{Binding Title}" Command="{Binding LurkingWifeModeCommand}" Height="19" VerticalAlignment="Center" BorderThickness="0" Margin="0" Padding="0" Background="Transparent" />
</StackPanel>
</TreeDataTemplate>

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/WalletExplorerViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/WalletExplorerViewModel.cs
@@ -2,6 +2,8 @@ using AvalonStudio.Extensibility;
using AvalonStudio.MVVM;
using AvalonStudio.Shell;
using ReactiveUI;
+using Splat;
+using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Composition;
using System.IO;
@@ -17,32 +19,35 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
[Shared]
public class WalletExplorerViewModel : ToolViewModel, IActivatableExtension
{
+ private ObservableCollection<WalletViewModelBase> _wallets;
+ private ViewModelBase _selectedItem;
+
public override Location DefaultLocation => Location.Right;
public WalletExplorerViewModel()
{
Title = "Wallet Explorer";
- _wallets = new ObservableCollection<WalletViewModel>();
+ _wallets = new ObservableCollection<WalletViewModelBase>();
+
+ WalletManager = Locator.Current.GetService<Global>().WalletManager;
}
- private ObservableCollection<WalletViewModel> _wallets;
+ private WalletManager WalletManager { get; }
- public ObservableCollection<WalletViewModel> Wallets
+ public ObservableCollection<WalletViewModelBase> Wallets
{
get => _wallets;
set => this.RaiseAndSetIfChanged(ref _wallets, value);
}
- private WasabiDocumentTabViewModel _selectedItem;
-
- public WasabiDocumentTabViewModel SelectedItem
+ public ViewModelBase SelectedItem
{
get => _selectedItem;
set => this.RaiseAndSetIfChanged(ref _selectedItem, value);
}
- internal void OpenWallet(Wallet wallet, bool receiveDominant)
+ internal void OpenWallet(Wallet wallet, bool receiveDominant, bool select)
{
var walletName = Path.GetFileNameWithoutExtension(wallet.KeyManager.FilePath);
if (_wallets.Any(x => x.Title == walletName))
@@ -51,10 +56,31 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
}
WalletViewModel walletViewModel = new WalletViewModel(wallet);
- _wallets.Add(walletViewModel);
+
+ Wallets.InsertSorted(walletViewModel);
+
+ if (select)
+ {
+ SelectedItem = walletViewModel;
+ }
+
walletViewModel.OpenWallet(receiveDominant);
}
+ internal void RemoveWallet(WalletViewModelBase wallet)
+ {
+ Wallets.Remove(wallet);
+ }
+
+ private void LoadWallets()
+ {
+ foreach (var walletPath in WalletManager.WalletDirectories.EnumerateWalletFiles())
+ {
+ Wallets.InsertSorted(new ClosedWalletViewModel(walletPath.FullName));
+ }
+ }
+
+
public void BeforeActivation()
{
}
@@ -62,6 +88,8 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
public void Activation()
{
IoC.Get<IShell>().MainPerspective.AddOrSelectTool(this);
+
+ LoadWallets();
}
}
}

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/WalletViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/WalletViewModel.cs
using NBitcoin;
using ReactiveUI;
+using Splat;
using System;
using System.Collections.ObjectModel;
-using System.IO;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
+using WalletWasabi.Blockchain.Keys;
using WalletWasabi.Gui.ViewModels;
+using WalletWasabi.Helpers;
using WalletWasabi.Logging;
-using Splat;
using WalletWasabi.Wallets;
-using WalletWasabi.Helpers;
-using WalletWasabi.Blockchain.Keys;
namespace WalletWasabi.Gui.Controls.WalletExplorer
{
- public class WalletViewModel : ViewModelBase
+ public class WalletViewModel : WalletViewModelBase
{
private ObservableCollection<WasabiDocumentTabViewModel> _actions;
- private bool _isExpanded;
- private string _title;
- public WalletViewModel(Wallet wallet)
+ public WalletViewModel(Wallet wallet) : base(wallet.WalletName)
{
Wallet = Guard.NotNull(nameof(wallet), wallet);
Disposables = Disposables is null ? new CompositeDisposable() : throw new NotSupportedException($"Cannot open {GetType().Name} before closing it.");
Actions = new ObservableCollection<WasabiDocumentTabViewModel>();
Global = Locator.Current.GetService<Global>();
- Title = WalletName;
LurkingWifeModeCommand = ReactiveCommand.Create(() =>
{
@@ -63,18 +59,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
public Global Global { get; }
- public bool IsExpanded
- {
- get => _isExpanded;
- set => this.RaiseAndSetIfChanged(ref _isExpanded, value);
- }
-
- public string Title
- {
- get => _title;
- set => this.RaiseAndSetIfChanged(ref _title, value);
- }
-
public string WalletName => Wallet.WalletName;
public KeyManager KeyManager => Wallet.KeyManager;
public Wallet Wallet { get; }

MODIFY
WalletWasabi.Gui/Tabs/WalletManager/LoadWallets/LoadWalletViewModel.cs
WalletWasabi.Gui/Tabs/WalletManager/LoadWallets/LoadWalletViewModel.cs
@@ -543,11 +543,11 @@ namespace WalletWasabi.Gui.Tabs.WalletManager.LoadWallets
if (wallet.Coins.Any())
{
// If already have coins then open the last active tab first.
- IoC.Get<WalletExplorerViewModel>().OpenWallet(wallet, receiveDominant: false);
+ IoC.Get<WalletExplorerViewModel>().OpenWallet(wallet, receiveDominant: false, true);
}
else // Else open with Receive tab first.
{
- IoC.Get<WalletExplorerViewModel>().OpenWallet(wallet, receiveDominant: true);
+ IoC.Get<WalletExplorerViewModel>().OpenWallet(wallet, receiveDominant: true, true);
}
}
catch (Exception ex)

Enable loading of multiple wallets.