Moved Wallet Creation options to the beginning of the Add wallet flow
MODIFY
WalletWasabi.Fluent/ViewModels/AddWallet/AddWalletPageViewModel.cs
WalletWasabi.Fluent/ViewModels/AddWallet/AddWalletPageViewModel.cs
-using ReactiveUI;
using System.IO;
using System.Reactive;
using System.Reactive.Disposables;
-using System.Reactive.Linq;
-using WalletWasabi.Blockchain.Keys;
-using WalletWasabi.Fluent.Validation;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using ReactiveUI;
+using WalletWasabi.Fluent.Helpers;
+using WalletWasabi.Fluent.Models;
using WalletWasabi.Fluent.ViewModels.Dialogs.Base;
-using WalletWasabi.Models;
-using WalletWasabi.Fluent.ViewModels.NavBar;
+using WalletWasabi.Helpers;
+using WalletWasabi.Logging;
namespace WalletWasabi.Fluent.ViewModels.AddWallet;
@@ -22,68 +23,65 @@ namespace WalletWasabi.Fluent.ViewModels.AddWallet;
NavBarPosition = NavBarPosition.Bottom)]
public partial class AddWalletPageViewModel : DialogViewModelBase<Unit>
{
- [AutoNotify] private string _walletName = "";
-
public AddWalletPageViewModel()
{
- SelectionMode = NavBarItemSelectionMode.Button;
+ CreateWalletCommand = ReactiveCommand.Create(OnCreateWallet);
- var canExecute = this.WhenAnyValue(x => x.WalletName)
- .ObserveOn(RxApp.MainThreadScheduler)
- .Select(x => !string.IsNullOrWhiteSpace(x) && !Validations.Any);
+ ConnectHardwareWalletCommand = ReactiveCommand.Create(OnConnectHardwareWallet);
- NextCommand = ReactiveCommand.Create(OnNext, canExecute);
+ ImportWalletCommand = ReactiveCommand.CreateFromTask(async () => await OnImportWalletAsync());
- OpenCommand = ReactiveCommand.Create(
- async () =>
- {
- MainViewModel.Instance.IsOobeBackgroundVisible = true;
+ RecoverWalletCommand = ReactiveCommand.Create(OnRecoverWallet);
- await NavigateDialogAsync(this, NavigationTarget.DialogScreen);
+ OpenCommand = ReactiveCommand.Create(async () => await NavigateDialogAsync(this, NavigationTarget.DialogScreen));
+ }
- MainViewModel.Instance.IsOobeBackgroundVisible = false;
- });
+ public ICommand CreateWalletCommand { get; }
- this.ValidateProperty(x => x.WalletName, errors => ValidateWalletName(errors, WalletName));
- }
+ public ICommand ConnectHardwareWalletCommand { get; }
- private void OnNext()
- {
- Navigate().To(new SelectWalletCreationOptionViewModel(WalletName));
- }
+ public ICommand ImportWalletCommand { get; }
- private static void ValidateWalletName(IValidationErrors errors, string walletName)
- {
- string walletFilePath = Path.Combine(Services.WalletManager.WalletDirectories.WalletsDir, $"{walletName}.json");
+ public ICommand RecoverWalletCommand { get; }
- if (string.IsNullOrEmpty(walletName))
+ private void OnCreateWallet()
{
- return;
+ Navigate().To(new WalletNamePageViewModel(WalletCreationOption.AddNewWallet));
}
- if (walletName.IsTrimmable())
+ private void OnConnectHardwareWallet()
{
- errors.Add(ErrorSeverity.Error, "Leading and trailing white spaces are not allowed!");
- return;
+ Navigate().To(new WalletNamePageViewModel(WalletCreationOption.ConnectToHardwareWallet));
}
- if (File.Exists(walletFilePath))
+ private async Task OnImportWalletAsync()
+ {
+ try
+ {
+ var filePath = await FileDialogHelper.ShowOpenFileDialogAsync("Import wallet file", new[] { "json" });
+
+ if (filePath is null)
{
- errors.Add(
- ErrorSeverity.Error,
- $"A wallet named {walletName} already exists. Please try a different name.");
return;
}
- if (!WalletGenerator.ValidateWalletName(walletName))
+ var walletName = Path.GetFileNameWithoutExtension(filePath);
+
+ var keyManager = await ImportWalletHelper.ImportWalletAsync(Services.WalletManager, walletName, filePath);
+
+ Navigate().To(new AddedWalletPageViewModel(keyManager));
+ }
+ catch (Exception ex)
{
- errors.Add(ErrorSeverity.Error, "Selected Wallet is not valid. Please try a different name.");
+ Logger.LogError(ex);
+ await ShowErrorAsync(Title, ex.ToUserFriendlyString(),
+ "The wallet file was not valid or compatible with Wasabi.");
}
}
- protected override void OnNavigatedFrom(bool isInHistory)
+ private void OnRecoverWallet()
{
- base.OnNavigatedFrom(isInHistory);
+ Navigate().To(new WalletNamePageViewModel(WalletCreationOption.RecoverWallet));
}
protected override void OnNavigatedTo(bool isInHistory, CompositeDisposable disposables)
@@ -92,12 +90,5 @@ public partial class AddWalletPageViewModel : DialogViewModelBase<Unit>
var enableCancel = Services.WalletManager.HasWallet();
SetupCancel(enableCancel: enableCancel, enableCancelOnEscape: enableCancel, enableCancelOnPressed: enableCancel);
-
- this.RaisePropertyChanged(WalletName);
-
- if (!isInHistory)
- {
- WalletName = "";
- }
}
}

DELETE
WalletWasabi.Fluent/ViewModels/AddWallet/SelectWalletCreationOptionViewModel.cs
-using System.Reactive.Disposables;
-using System.Threading.Tasks;
-using System.Windows.Input;
-using ReactiveUI;
-using WalletWasabi.Blockchain.Keys;
-using WalletWasabi.Fluent.Helpers;
-using WalletWasabi.Fluent.ViewModels.AddWallet.Create;
-using WalletWasabi.Fluent.ViewModels.AddWallet.HardwareWallet;
-using WalletWasabi.Fluent.ViewModels.Dialogs;
-using WalletWasabi.Fluent.ViewModels.Navigation;
-using WalletWasabi.Helpers;
-using WalletWasabi.Logging;
-
-namespace WalletWasabi.Fluent.ViewModels.AddWallet;
-
-[NavigationMetaData(Title = "Select an option")]
-public partial class SelectWalletCreationOptionViewModel : RoutableViewModel
-{
- public SelectWalletCreationOptionViewModel(string walletName)
- {
- EnableBack = true;
-
- RecoverWalletCommand = ReactiveCommand.Create(() => OnRecoverWallet(walletName));
-
- ImportWalletCommand = ReactiveCommand.CreateFromTask(async () => await OnImportWalletAsync(walletName));
-
- ConnectHardwareWalletCommand = ReactiveCommand.Create(() => OnConnectHardwareWallet(walletName));
-
- CreateWalletCommand = ReactiveCommand.CreateFromTask(async () => await OnCreateWalletAsync(walletName));
- }
-
- public ICommand CreateWalletCommand { get; }
-
- public ICommand RecoverWalletCommand { get; }
-
- public ICommand ImportWalletCommand { get; }
-
- public ICommand ConnectHardwareWalletCommand { get; }
-
- private void OnRecoverWallet(string walletName)
- {
- Navigate().To(new RecoverWalletViewModel(walletName));
- }
-
- private void OnConnectHardwareWallet(string walletName)
- {
- Navigate().To(new ConnectHardwareWalletViewModel(walletName));
- }
-
- private async Task OnImportWalletAsync(string walletName)
- {
- try
- {
- var filePath = await FileDialogHelper.ShowOpenFileDialogAsync("Import wallet file", new[] { "json" });
-
- if (filePath is null)
- {
- return;
- }
-
- var keyManager = await ImportWalletHelper.ImportWalletAsync(Services.WalletManager, walletName, filePath);
-
- Navigate().To(new AddedWalletPageViewModel(keyManager));
- }
- catch (Exception ex)
- {
- Logger.LogError(ex);
- await ShowErrorAsync(Title, ex.ToUserFriendlyString(),
- "The wallet file was not valid or compatible with Wasabi.");
- }
- }
-
- private async Task OnCreateWalletAsync(string walletName)
- {
- var dialogResult = await NavigateDialogAsync(
- new CreatePasswordDialogViewModel("Create Password", "Enter a password for your wallet.", enableEmpty: true)
- , NavigationTarget.CompactDialogScreen);
-
- if (dialogResult.Result is { } password)
- {
- IsBusy = true;
-
- var (km, mnemonic) = await Task.Run(
- () =>
- {
- var walletGenerator = new WalletGenerator(
- Services.WalletManager.WalletDirectories.WalletsDir,
- Services.WalletManager.Network)
- {
- TipHeight = Services.BitcoinStore.SmartHeaderChain.TipHeight
- };
- return walletGenerator.GenerateWallet(walletName, password);
- });
-
- Navigate().To(new RecoveryWordsViewModel(km, mnemonic));
-
- IsBusy = false;
- }
- }
-
- protected override void OnNavigatedTo(bool isInHistory, CompositeDisposable disposables)
- {
- base.OnNavigatedTo(isInHistory, disposables);
-
- var enableCancel = Services.WalletManager.HasWallet();
- SetupCancel(enableCancel: enableCancel, enableCancelOnEscape: enableCancel, enableCancelOnPressed: false);
- }
-}

ADD
WalletWasabi.Fluent/ViewModels/AddWallet/WalletNamePageViewModel.cs
+using ReactiveUI;
+using System.IO;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using WalletWasabi.Blockchain.Keys;
+using WalletWasabi.Fluent.Validation;
+using WalletWasabi.Models;
+using WalletWasabi.Fluent.ViewModels.Dialogs;
+using System.Threading.Tasks;
+using WalletWasabi.Fluent.ViewModels.AddWallet.Create;
+using WalletWasabi.Fluent.Models;
+using WalletWasabi.Fluent.ViewModels.AddWallet.HardwareWallet;
+using WalletWasabi.Fluent.ViewModels.Navigation;
+
+namespace WalletWasabi.Fluent.ViewModels.AddWallet;
+
+[NavigationMetaData(Title = "Enter wallet name")]
+public partial class WalletNamePageViewModel : RoutableViewModel
+{
+ [AutoNotify] private string _walletName = "";
+
+ public WalletNamePageViewModel(WalletCreationOption creationOption)
+ {
+ EnableBack = true;
+
+ var canExecute = this.WhenAnyValue(x => x.WalletName)
+ .ObserveOn(RxApp.MainThreadScheduler)
+ .Select(x => !string.IsNullOrWhiteSpace(x) && !Validations.Any);
+
+ NextCommand = ReactiveCommand.CreateFromTask(async () => await OnNextAsync(WalletName, creationOption), canExecute);
+
+ this.ValidateProperty(x => x.WalletName, errors => ValidateWalletName(errors, WalletName));
+ }
+
+ private async Task OnNextAsync(string walletName, WalletCreationOption creationOption)
+ {
+ switch (creationOption)
+ {
+ case WalletCreationOption.AddNewWallet:
+ await CreatePasswordAsync(walletName);
+ break;
+ case WalletCreationOption.ConnectToHardwareWallet:
+ Navigate().To(new ConnectHardwareWalletViewModel(walletName));
+ break;
+ case WalletCreationOption.RecoverWallet:
+ Navigate().To(new RecoverWalletViewModel(walletName));
+ break;
+ default:
+ throw new InvalidOperationException($"WalletCreationOption not supported: {creationOption}");
+ }
+ }
+
+ private async Task CreatePasswordAsync(string walletName)
+ {
+ var dialogResult = await NavigateDialogAsync(
+ new CreatePasswordDialogViewModel("Create Password", "Enter a password for your wallet.", enableEmpty: true)
+ , NavigationTarget.CompactDialogScreen);
+
+ if (dialogResult.Result is { } password)
+ {
+ IsBusy = true;
+
+ var (km, mnemonic) = await Task.Run(
+ () =>
+ {
+ var walletGenerator = new WalletGenerator(
+ Services.WalletManager.WalletDirectories.WalletsDir,
+ Services.WalletManager.Network)
+ {
+ TipHeight = Services.BitcoinStore.SmartHeaderChain.TipHeight
+ };
+ return walletGenerator.GenerateWallet(walletName, password);
+ });
+
+ Navigate().To(new RecoveryWordsViewModel(km, mnemonic));
+
+ IsBusy = false;
+ }
+ }
+
+ private static void ValidateWalletName(IValidationErrors errors, string walletName)
+ {
+ string walletFilePath = Path.Combine(Services.WalletManager.WalletDirectories.WalletsDir, $"{walletName}.json");
+
+ if (string.IsNullOrEmpty(walletName))
+ {
+ return;
+ }
+
+ if (walletName.IsTrimmable())
+ {
+ errors.Add(ErrorSeverity.Error, "Leading and trailing white spaces are not allowed!");
+ return;
+ }
+
+ if (File.Exists(walletFilePath))
+ {
+ errors.Add(
+ ErrorSeverity.Error,
+ $"A wallet named {walletName} already exists. Please try a different name.");
+ return;
+ }
+
+ if (!WalletGenerator.ValidateWalletName(walletName))
+ {
+ errors.Add(ErrorSeverity.Error, "Selected Wallet is not valid. Please try a different name.");
+ }
+ }
+
+ protected override void OnNavigatedTo(bool isInHistory, CompositeDisposable disposables)
+ {
+ base.OnNavigatedTo(isInHistory, disposables);
+
+ var enableCancel = Services.WalletManager.HasWallet();
+ SetupCancel(enableCancel: enableCancel, enableCancelOnEscape: enableCancel, enableCancelOnPressed: enableCancel);
+
+ this.RaisePropertyChanged(WalletName);
+
+ if (!isInHistory)
+ {
+ WalletName = "";
+ }
+ }
+}

MODIFY
WalletWasabi.Fluent/Views/AddWallet/AddWalletPageView.axaml
WalletWasabi.Fluent/Views/AddWallet/AddWalletPageView.axaml
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:WalletWasabi.Fluent.ViewModels.AddWallet"
xmlns:c="using:WalletWasabi.Fluent.Controls"
- xmlns:i="using:Avalonia.Xaml.Interactivity"
- xmlns:behaviors="using:WalletWasabi.Fluent.Behaviors"
mc:Ignorable="d" d:DesignWidth="428" d:DesignHeight="371"
x:DataType="vm:AddWalletPageViewModel"
x:CompileBindings="True"
x:Class="WalletWasabi.Fluent.Views.AddWallet.AddWalletPageView">
- <c:ContentArea Title="{Binding Title}" Caption="Type in your wallet's name"
+ <c:ContentArea Title="{Binding Title}"
EnableCancel="{Binding EnableCancel}"
EnableBack="{Binding EnableBack}"
- IsBusy="{Binding IsBusy}"
- EnableNext="True" NextContent="Continue">
- <StackPanel DockPanel.Dock="Top" Margin="0 0 0 20">
- <Label Content="Wallet _Name:" Target="walletNameTb" />
- <TextBox x:Name="walletNameTb" MaxLength="50" Text="{Binding WalletName}" Watermark="(e.g. My Wallet)">
- <i:Interaction.Behaviors>
- <behaviors:FocusOnAttachedBehavior />
- </i:Interaction.Behaviors>
- </TextBox>
- </StackPanel>
+ IsBusy="{Binding IsBusy}">
+ <WrapPanel Orientation="Horizontal" Margin="0 0 0 20" HorizontalAlignment="Center">
+ <WrapPanel.Styles>
+ <Style Selector="c|TileButton">
+ <Setter Property="Margin" Value="4"/>
+ </Style>
+ </WrapPanel.Styles>
+ <c:TileButton Text="Create a new wallet"
+ Classes="action"
+ Icon="{StaticResource add_regular}"
+ Command="{Binding CreateWalletCommand}"
+ IsDefault="{Binding IsActive}" />
+ <c:TileButton Text="Connect to hardware wallet"
+ Icon="{StaticResource calculator_regular}"
+ Command="{Binding ConnectHardwareWalletCommand}" />
+ <c:TileButton Text="Import a wallet"
+ Icon="{StaticResource import_regular}"
+ Command="{Binding ImportWalletCommand}" />
+ <c:TileButton Text="Recover a wallet"
+ Icon="{StaticResource recover_arrow_right_regular}"
+ Command="{Binding RecoverWalletCommand}" />
+ </WrapPanel>
</c:ContentArea>
</UserControl>

DELETE
WalletWasabi.Fluent/Views/AddWallet/SelectWalletCreationOptionView.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"
- xmlns:vm="using:WalletWasabi.Fluent.ViewModels.AddWallet"
- xmlns:c="using:WalletWasabi.Fluent.Controls"
- mc:Ignorable="d" d:DesignWidth="428" d:DesignHeight="371"
- x:DataType="vm:SelectWalletCreationOptionViewModel"
- x:CompileBindings="True"
- x:Class="WalletWasabi.Fluent.Views.AddWallet.SelectWalletCreationOptionView">
- <c:ContentArea Title="{Binding Title}"
- EnableCancel="{Binding EnableCancel}"
- EnableBack="{Binding EnableBack}"
- IsBusy="{Binding IsBusy}">
- <WrapPanel Orientation="Horizontal" Margin="0 0 0 20" HorizontalAlignment="Center">
- <WrapPanel.Styles>
- <Style Selector="c|TileButton">
- <Setter Property="Margin" Value="4"/>
- </Style>
- </WrapPanel.Styles>
- <c:TileButton Text="Create a new wallet"
- Classes="action"
- Icon="{StaticResource add_regular}"
- Command="{Binding CreateWalletCommand}"
- IsDefault="{Binding IsActive}" />
- <c:TileButton Text="Connect to hardware wallet"
- Icon="{StaticResource calculator_regular}"
- Command="{Binding ConnectHardwareWalletCommand}" />
- <c:TileButton Text="Import a wallet"
- Icon="{StaticResource import_regular}"
- Command="{Binding ImportWalletCommand}" />
- <c:TileButton Text="Recover a wallet"
- Icon="{StaticResource recover_arrow_right_regular}"
- Command="{Binding RecoverWalletCommand}" />
- </WrapPanel>
- </c:ContentArea>
-</UserControl>

ADD
WalletWasabi.Fluent/Views/AddWallet/WalletNamePageView.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"
+ xmlns:vm="using:WalletWasabi.Fluent.ViewModels.AddWallet"
+ xmlns:c="using:WalletWasabi.Fluent.Controls"
+ xmlns:i="using:Avalonia.Xaml.Interactivity"
+ xmlns:behaviors="using:WalletWasabi.Fluent.Behaviors"
+ mc:Ignorable="d" d:DesignWidth="428" d:DesignHeight="371"
+ x:DataType="vm:WalletNamePageViewModel"
+ x:CompileBindings="True"
+ x:Class="WalletWasabi.Fluent.Views.AddWallet.WalletNamePageView">
+ <c:ContentArea Title="{Binding Title}" Caption="Type in your wallet's name"
+ EnableCancel="{Binding EnableCancel}"
+ EnableBack="{Binding EnableBack}"
+ IsBusy="{Binding IsBusy}"
+ EnableNext="True" NextContent="Continue">
+ <StackPanel DockPanel.Dock="Top" Margin="0 0 0 20">
+ <Label Content="Wallet _Name:" Target="walletNameTb" />
+ <TextBox x:Name="walletNameTb" MaxLength="50" Text="{Binding WalletName}" Watermark="(e.g. My Wallet)">
+ <i:Interaction.Behaviors>
+ <behaviors:FocusOnAttachedBehavior />
+ </i:Interaction.Behaviors>
+ </TextBox>
+ </StackPanel>
+ </c:ContentArea>
+</UserControl>

RENAME
WalletWasabi.Fluent/Views/AddWallet/SelectWalletCreationOptionView.axaml.cs
WalletWasabi.Fluent/Views/AddWallet/WalletNamePageView.axaml.cs
@@ -3,9 +3,9 @@ using Avalonia.Markup.Xaml;
namespace WalletWasabi.Fluent.Views.AddWallet;
-public class SelectWalletCreationOptionView : UserControl
+public class WalletNamePageView : UserControl
{
- public SelectWalletCreationOptionView()
+ public WalletNamePageView()
{
InitializeComponent();
}

Moved Wallet Creation options to the beginning of the Add wallet flow

(cherry picked from commit ab89bc98b4033aaeb12f043818e6893ab6fa616e)
(cherry picked from commit c694abd2a251abdc61f95483eb4d452b3af2bcae)