feat(app): step-by-step setup wizard instead of single-form panel
Startup now follows the blueprint §15 flow, one field per screen with Avanti/Indietro: initial choice (open / create / restore), seed display with write-it-down warning, seed confirmation by re-typing, mnemonic validation on restore, optional BIP39 passphrase, and wallet file password as the final step. Opening an encrypted file from the menu routes to the same password step.
This commit is contained in:
@@ -69,11 +69,43 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string statusMessage = "";
|
private string statusMessage = "";
|
||||||
|
|
||||||
// ---- pannello setup ----
|
// ---- wizard di setup (§15): un passo alla volta ----
|
||||||
|
|
||||||
|
public const string StepStart = "start";
|
||||||
|
public const string StepOpen = "open";
|
||||||
|
public const string StepShowSeed = "show-seed";
|
||||||
|
public const string StepConfirmSeed = "confirm-seed";
|
||||||
|
public const string StepWords = "words";
|
||||||
|
public const string StepPassphrase = "passphrase";
|
||||||
|
public const string StepPassword = "password";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(IsStepStart))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(IsStepOpen))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(IsStepShowSeed))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(IsStepConfirmSeed))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(IsStepWords))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(IsStepPassphrase))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(IsStepPassword))]
|
||||||
|
private string setupStep = StepStart;
|
||||||
|
|
||||||
|
public bool IsStepStart => SetupStep == StepStart;
|
||||||
|
public bool IsStepOpen => SetupStep == StepOpen;
|
||||||
|
public bool IsStepShowSeed => SetupStep == StepShowSeed;
|
||||||
|
public bool IsStepConfirmSeed => SetupStep == StepConfirmSeed;
|
||||||
|
public bool IsStepWords => SetupStep == StepWords;
|
||||||
|
public bool IsStepPassphrase => SetupStep == StepPassphrase;
|
||||||
|
public bool IsStepPassword => SetupStep == StepPassword;
|
||||||
|
|
||||||
|
/// <summary>True quando il flusso è "ripristina" (parole inserite dall'utente).</summary>
|
||||||
|
private bool _isRestoreFlow;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string mnemonicInput = "";
|
private string mnemonicInput = "";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string confirmMnemonicInput = "";
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string passphraseInput = "";
|
private string passphraseInput = "";
|
||||||
|
|
||||||
@@ -180,10 +212,12 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
|
|
||||||
private void RefreshSetupState()
|
private void RefreshSetupState()
|
||||||
{
|
{
|
||||||
|
SetupStep = StepStart;
|
||||||
|
MnemonicInput = ConfirmMnemonicInput = PassphraseInput = PasswordInput = "";
|
||||||
WalletFileExists = WalletStore.Exists(AppPaths.DefaultWalletPath(Net));
|
WalletFileExists = WalletStore.Exists(AppPaths.DefaultWalletPath(Net));
|
||||||
StatusMessage = WalletFileExists
|
StatusMessage = WalletFileExists
|
||||||
? "Trovato un wallet esistente: inserisci la password (se impostata) e apri."
|
? "Trovato un wallet esistente su questa rete: aprilo, oppure creane un altro."
|
||||||
: "Nessun wallet su questa rete: creane uno nuovo o ripristina da seed.";
|
: "Benvenuto: crea un nuovo wallet o ripristina da seed.";
|
||||||
RefreshServers();
|
RefreshServers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,13 +243,97 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
ServerInput = $"{server.Host}:{server.PortFor(value)}";
|
ServerInput = $"{server.Host}:{server.PortFor(value)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- comandi setup ----------
|
// ---------- comandi del wizard (§15): un passo alla volta ----------
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void GenerateMnemonic()
|
private void WizardStartOpen()
|
||||||
{
|
{
|
||||||
|
PasswordInput = "";
|
||||||
|
SetupStep = StepOpen;
|
||||||
|
StatusMessage = "Inserisci la password del file (lascia vuoto se non impostata).";
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void WizardStartNew()
|
||||||
|
{
|
||||||
|
_isRestoreFlow = false;
|
||||||
MnemonicInput = Bip39.Generate(MnemonicLength.Twelve).ToString();
|
MnemonicInput = Bip39.Generate(MnemonicLength.Twelve).ToString();
|
||||||
StatusMessage = "Nuova mnemonica generata: SCRIVILA SU CARTA prima di continuare.";
|
SetupStep = StepShowSeed;
|
||||||
|
StatusMessage = "Scrivi le 12 parole SU CARTA, nell'ordine. Sono l'unico backup del wallet.";
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void WizardStartRestore()
|
||||||
|
{
|
||||||
|
_isRestoreFlow = true;
|
||||||
|
MnemonicInput = "";
|
||||||
|
SetupStep = StepWords;
|
||||||
|
StatusMessage = "Inserisci la mnemonica BIP39 (12 o 24 parole separate da spazi).";
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void WizardNextFromShowSeed()
|
||||||
|
{
|
||||||
|
ConfirmMnemonicInput = "";
|
||||||
|
SetupStep = StepConfirmSeed;
|
||||||
|
StatusMessage = "Reinserisci le 12 parole per confermare di averle scritte.";
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void WizardNextFromConfirmSeed()
|
||||||
|
{
|
||||||
|
var normalized = string.Join(' ',
|
||||||
|
ConfirmMnemonicInput.Split(' ', StringSplitOptions.RemoveEmptyEntries));
|
||||||
|
if (!string.Equals(normalized, MnemonicInput, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
StatusMessage = "Le parole non corrispondono: ricontrolla quello che hai scritto su carta.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GoToPassphraseStep();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void WizardNextFromWords()
|
||||||
|
{
|
||||||
|
if (!Bip39.TryParse(MnemonicInput, out _))
|
||||||
|
{
|
||||||
|
StatusMessage = "Mnemonica non valida (parole o checksum errati): ricontrolla.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GoToPassphraseStep();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GoToPassphraseStep()
|
||||||
|
{
|
||||||
|
PassphraseInput = "";
|
||||||
|
SetupStep = StepPassphrase;
|
||||||
|
StatusMessage = "Passphrase BIP39 opzionale: cambia completamente il wallet. " +
|
||||||
|
"Se la usi, annotala A PARTE dal seed; se la perdi i fondi sono irrecuperabili. " +
|
||||||
|
"Lascia vuoto per non usarla.";
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void WizardNextFromPassphrase()
|
||||||
|
{
|
||||||
|
PasswordInput = "";
|
||||||
|
SetupStep = StepPassword;
|
||||||
|
StatusMessage = "Password di cifratura del file wallet su disco (consigliata). " +
|
||||||
|
"Non sostituisce il seed: serve solo a proteggere il file.";
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void WizardBack()
|
||||||
|
{
|
||||||
|
SetupStep = SetupStep switch
|
||||||
|
{
|
||||||
|
StepOpen or StepShowSeed or StepWords => StepStart,
|
||||||
|
StepConfirmSeed => StepShowSeed,
|
||||||
|
StepPassphrase => _isRestoreFlow ? StepWords : StepConfirmSeed,
|
||||||
|
StepPassword => StepPassphrase,
|
||||||
|
_ => StepStart,
|
||||||
|
};
|
||||||
|
if (SetupStep == StepStart)
|
||||||
|
RefreshSetupState();
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
@@ -275,11 +393,13 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
catch (WrongPasswordException)
|
catch (WrongPasswordException)
|
||||||
{
|
{
|
||||||
// Cifrato: si chiede la password nel pannello di apertura.
|
// Cifrato: si chiede la password nel passo di apertura del wizard.
|
||||||
if (IsWalletOpen)
|
if (IsWalletOpen)
|
||||||
CloseWallet();
|
CloseWallet();
|
||||||
_pendingOpenPath = path;
|
_pendingOpenPath = path;
|
||||||
WalletFileExists = true;
|
WalletFileExists = true;
|
||||||
|
PasswordInput = "";
|
||||||
|
SetupStep = StepOpen;
|
||||||
StatusMessage = $"Il wallet \"{Path.GetFileName(path)}\" è cifrato: inserisci la password e premi Apri.";
|
StatusMessage = $"Il wallet \"{Path.GetFileName(path)}\" è cifrato: inserisci la password e premi Apri.";
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -306,7 +426,8 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
_account = account;
|
_account = account;
|
||||||
_walletPath = path;
|
_walletPath = path;
|
||||||
_password = password;
|
_password = password;
|
||||||
MnemonicInput = PassphraseInput = PasswordInput = "";
|
MnemonicInput = ConfirmMnemonicInput = PassphraseInput = PasswordInput = "";
|
||||||
|
SetupStep = StepStart;
|
||||||
|
|
||||||
NetworkInfo = $"{doc.Network} · {doc.ScriptKind} · m/{doc.AccountPath}"
|
NetworkInfo = $"{doc.Network} · {doc.ScriptKind} · m/{doc.AccountPath}"
|
||||||
+ (doc.IsWatchOnly ? " · watch-only" : "");
|
+ (doc.IsWatchOnly ? " · watch-only" : "");
|
||||||
|
|||||||
@@ -31,44 +31,107 @@
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
<!-- ============ PANNELLO SETUP: crea / ripristina / apri ============ -->
|
<!-- ============ WIZARD DI SETUP (§15): un passo alla volta ============ -->
|
||||||
<ScrollViewer Grid.Row="1" IsVisible="{Binding IsSetupVisible}">
|
<ScrollViewer Grid.Row="1" IsVisible="{Binding IsSetupVisible}">
|
||||||
<StackPanel MaxWidth="560" Margin="24" Spacing="14">
|
<StackPanel MaxWidth="560" Margin="24,40" Spacing="18"
|
||||||
<TextBlock Text="Palladium Wallet" FontSize="28" FontWeight="Bold"/>
|
HorizontalAlignment="Center">
|
||||||
|
<TextBlock Text="Palladium Wallet" FontSize="28" FontWeight="Bold"
|
||||||
|
HorizontalAlignment="Center"/>
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
<!-- Passo 1: scelta iniziale -->
|
||||||
|
<StackPanel IsVisible="{Binding IsStepStart}" Spacing="12">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Center">
|
||||||
<TextBlock Text="Rete:" VerticalAlignment="Center"/>
|
<TextBlock Text="Rete:" VerticalAlignment="Center"/>
|
||||||
<ComboBox ItemsSource="{Binding Networks}"
|
<ComboBox ItemsSource="{Binding Networks}"
|
||||||
SelectedItem="{Binding SelectedNetwork}"
|
SelectedItem="{Binding SelectedNetwork}" MinWidth="140"/>
|
||||||
MinWidth="140"/>
|
</StackPanel>
|
||||||
|
<Button Content="Apri il wallet esistente" FontSize="16"
|
||||||
|
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
||||||
|
IsVisible="{Binding WalletFileExists}"
|
||||||
|
Command="{Binding WizardStartOpenCommand}"/>
|
||||||
|
<Button Content="Crea un nuovo wallet" FontSize="16"
|
||||||
|
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
||||||
|
Command="{Binding WizardStartNewCommand}"/>
|
||||||
|
<Button Content="Ripristina da seed" FontSize="16"
|
||||||
|
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
||||||
|
Command="{Binding WizardStartRestoreCommand}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Apri wallet esistente -->
|
<!-- Passo: password del wallet esistente -->
|
||||||
<Border IsVisible="{Binding WalletFileExists}"
|
<StackPanel IsVisible="{Binding IsStepOpen}" Spacing="12">
|
||||||
BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="1"
|
<TextBlock Text="Apri il wallet" FontSize="18" FontWeight="Bold"/>
|
||||||
CornerRadius="6" Padding="14">
|
|
||||||
<StackPanel Spacing="8">
|
|
||||||
<TextBlock Text="Apri il wallet esistente" FontWeight="Bold"/>
|
|
||||||
<TextBox PlaceholderText="Password del file (vuoto se non impostata)"
|
<TextBox PlaceholderText="Password del file (vuoto se non impostata)"
|
||||||
PasswordChar="●" Text="{Binding PasswordInput}"/>
|
PasswordChar="●" Text="{Binding PasswordInput}"/>
|
||||||
<Button Content="Apri wallet" Command="{Binding OpenExistingCommand}"/>
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||||
|
<Button Content="Indietro" Command="{Binding WizardBackCommand}"/>
|
||||||
|
<Button Content="Apri" Classes="accent" Command="{Binding OpenExistingCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- Crea / ripristina -->
|
<!-- Passo: mostra il nuovo seed -->
|
||||||
<Border BorderBrush="Gray" BorderThickness="1" CornerRadius="6" Padding="14">
|
<StackPanel IsVisible="{Binding IsStepShowSeed}" Spacing="12">
|
||||||
<StackPanel Spacing="8">
|
<TextBlock Text="Il tuo seed (12 parole)" FontSize="18" FontWeight="Bold"/>
|
||||||
<TextBlock Text="Crea nuovo o ripristina da seed" FontWeight="Bold"/>
|
<Border BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="1"
|
||||||
<TextBox PlaceholderText="Mnemonica BIP39 (12 o 24 parole)"
|
CornerRadius="6" Padding="14">
|
||||||
AcceptsReturn="False" Text="{Binding MnemonicInput}"/>
|
<SelectableTextBlock Text="{Binding MnemonicInput}"
|
||||||
<Button Content="Genera nuova mnemonica" Command="{Binding GenerateMnemonicCommand}"/>
|
FontFamily="monospace" FontSize="16" TextWrapping="Wrap"/>
|
||||||
<TextBox PlaceholderText="Passphrase BIP39 opzionale (cambia il wallet! annotala a parte)"
|
|
||||||
Text="{Binding PassphraseInput}"/>
|
|
||||||
<TextBox PlaceholderText="Password di cifratura del file wallet (consigliata)"
|
|
||||||
PasswordChar="●" Text="{Binding PasswordInput}"/>
|
|
||||||
<Button Content="Crea / ripristina wallet" Command="{Binding CreateOrRestoreCommand}"/>
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
</Border>
|
||||||
|
<TextBlock Foreground="Orange" TextWrapping="Wrap"
|
||||||
|
Text="Scrivi le parole su carta, nell'ordine. Chi le possiede controlla i fondi; se le perdi, i fondi sono irrecuperabili."/>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||||
|
<Button Content="Indietro" Command="{Binding WizardBackCommand}"/>
|
||||||
|
<Button Content="Le ho scritte — Avanti" Classes="accent"
|
||||||
|
Command="{Binding WizardNextFromShowSeedCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Passo: conferma del seed -->
|
||||||
|
<StackPanel IsVisible="{Binding IsStepConfirmSeed}" Spacing="12">
|
||||||
|
<TextBlock Text="Conferma il seed" FontSize="18" FontWeight="Bold"/>
|
||||||
|
<TextBox PlaceholderText="Reinserisci le 12 parole separate da spazi"
|
||||||
|
AcceptsReturn="False" Text="{Binding ConfirmMnemonicInput}"/>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||||
|
<Button Content="Indietro" Command="{Binding WizardBackCommand}"/>
|
||||||
|
<Button Content="Avanti" Classes="accent"
|
||||||
|
Command="{Binding WizardNextFromConfirmSeedCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Passo: inserimento seed (ripristino) -->
|
||||||
|
<StackPanel IsVisible="{Binding IsStepWords}" Spacing="12">
|
||||||
|
<TextBlock Text="Ripristina da seed" FontSize="18" FontWeight="Bold"/>
|
||||||
|
<TextBox PlaceholderText="Mnemonica BIP39 (12 o 24 parole separate da spazi)"
|
||||||
|
AcceptsReturn="False" Text="{Binding MnemonicInput}"/>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||||
|
<Button Content="Indietro" Command="{Binding WizardBackCommand}"/>
|
||||||
|
<Button Content="Avanti" Classes="accent"
|
||||||
|
Command="{Binding WizardNextFromWordsCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Passo: passphrase opzionale -->
|
||||||
|
<StackPanel IsVisible="{Binding IsStepPassphrase}" Spacing="12">
|
||||||
|
<TextBlock Text="Passphrase opzionale" FontSize="18" FontWeight="Bold"/>
|
||||||
|
<TextBox PlaceholderText="Lascia vuoto per non usarla"
|
||||||
|
Text="{Binding PassphraseInput}"/>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||||
|
<Button Content="Indietro" Command="{Binding WizardBackCommand}"/>
|
||||||
|
<Button Content="Avanti" Classes="accent"
|
||||||
|
Command="{Binding WizardNextFromPassphraseCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Passo finale: password del file -->
|
||||||
|
<StackPanel IsVisible="{Binding IsStepPassword}" Spacing="12">
|
||||||
|
<TextBlock Text="Password del file wallet" FontSize="18" FontWeight="Bold"/>
|
||||||
|
<TextBox PlaceholderText="Consigliata (vuoto = file in chiaro su disco)"
|
||||||
|
PasswordChar="●" Text="{Binding PasswordInput}"/>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||||
|
<Button Content="Indietro" Command="{Binding WizardBackCommand}"/>
|
||||||
|
<Button Content="Crea il wallet" Classes="accent"
|
||||||
|
Command="{Binding CreateOrRestoreCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user