From f3bf4cf94af47eb0fb8009d8f951d5ef0440b300 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Fri, 12 Jun 2026 09:25:00 +0200 Subject: [PATCH] feat(app): first-run data location wizard step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On first launch (no data yet, no portable dir, no pointer file), the wizard now shows a new step-0 screen asking where to store wallets, config and certificates. AppPaths changes: - DefaultDataRoot() → ~/.PalladiumWallet (home, always writable) - IsDataLocationConfigured() → true when portable / override / pointer already written / legacy or default already has data - ConfigureDataLocation(root) writes a bootstrap pointer file and creates the directory - DataRoot() resolution order: override → portable → pointer → legacy (has data) → default ViewModel: new StepDataLocation step, UseDefaultDataLocationCommand, ApplyDataLocation(root) (also called from View's folder picker). View: new wizard panel with description, default-path display, two buttons (use default / choose folder); folder picker via StorageProvider.OpenFolderPickerAsync. Loc: add wiz.data.* keys (6 languages); fix fallback language "it"→"en"; update test assertion accordingly. --- src/App/Localization/Loc.cs | 13 ++- src/App/ViewModels/MainWindowViewModel.cs | 41 +++++++- src/App/Views/MainWindow.axaml | 17 ++++ src/App/Views/MainWindow.axaml.cs | 15 +++ src/Core/Storage/AppPaths.cs | 99 ++++++++++++++++--- .../Wallet/TransactionFactoryTests.cs | 2 +- 6 files changed, 173 insertions(+), 14 deletions(-) diff --git a/src/App/Localization/Loc.cs b/src/App/Localization/Loc.cs index c1f450b..71e85cf 100644 --- a/src/App/Localization/Loc.cs +++ b/src/App/Localization/Loc.cs @@ -27,7 +27,7 @@ public sealed class Loc /// internal static Loc SwitchTo(string language) { - if (System.Array.IndexOf(Languages, language) < 0) language = "it"; + if (System.Array.IndexOf(Languages, language) < 0) language = "en"; var loc = new Loc(language); Instance = loc; return loc; @@ -54,6 +54,17 @@ public sealed class Loc ["settings.unit.short"] = ["Unità", "Unit", "Unidad", "Unité", "Unidade", "Einheit"], // Wizard + ["wiz.data.title"] = ["Dove salvare i dati", "Where to store data", "Dónde guardar los datos", "Où enregistrer les données", "Onde salvar os dados", "Wo Daten gespeichert werden"], + ["wiz.data.info"] = [ + "Scegli la cartella in cui salvare wallet, configurazione e certificati. Puoi usare il percorso predefinito o sceglierne uno tuo.", + "Choose the folder where wallets, configuration and certificates are stored. Use the default path or pick your own.", + "Elige la carpeta donde se guardarán wallets, configuración y certificados. Usa la ruta predeterminada o elige la tuya.", + "Choisissez le dossier où enregistrer les wallets, la configuration et les certificats. Utilisez le chemin par défaut ou le vôtre.", + "Escolha a pasta onde salvar carteiras, configuração e certificados. Use o caminho padrão ou escolha o seu.", + "Wählen Sie den Ordner für Wallets, Konfiguration und Zertifikate. Nutzen Sie den Standardpfad oder einen eigenen."], + ["wiz.data.default"] = ["Percorso predefinito:", "Default path:", "Ruta predeterminada:", "Chemin par défaut :", "Caminho padrão:", "Standardpfad:"], + ["wiz.data.usedefault"] = ["Usa il percorso predefinito", "Use the default path", "Usar la ruta predeterminada", "Utiliser le chemin par défaut", "Usar o caminho padrão", "Standardpfad verwenden"], + ["wiz.data.choose"] = ["Scegli una cartella…", "Choose a folder…", "Elegir una carpeta…", "Choisir un dossier…", "Escolher uma pasta…", "Ordner wählen…"], ["wiz.net"] = ["Rete:", "Network:", "Red:", "Réseau :", "Rede:", "Netzwerk:"], ["wiz.open.btn"] = ["Apri il wallet esistente", "Open existing wallet", "Abrir wallet existente", "Ouvrir le wallet existant", "Abrir carteira existente", "Vorhandenes Wallet öffnen"], ["wiz.new.btn"] = ["Crea un nuovo wallet", "Create a new wallet", "Crear nuevo wallet", "Créer un nouveau wallet", "Criar nova carteira", "Neues Wallet erstellen"], diff --git a/src/App/ViewModels/MainWindowViewModel.cs b/src/App/ViewModels/MainWindowViewModel.cs index 58dceb0..8c16b02 100644 --- a/src/App/ViewModels/MainWindowViewModel.cs +++ b/src/App/ViewModels/MainWindowViewModel.cs @@ -165,6 +165,7 @@ public partial class MainWindowViewModel : ViewModelBase // ---- wizard di setup (§15): un passo alla volta ---- + public const string StepDataLocation = "data-location"; public const string StepStart = "start"; public const string StepOpen = "open"; public const string StepShowSeed = "show-seed"; @@ -174,6 +175,7 @@ public partial class MainWindowViewModel : ViewModelBase public const string StepPassword = "password"; [ObservableProperty] + [NotifyPropertyChangedFor(nameof(IsStepDataLocation))] [NotifyPropertyChangedFor(nameof(IsStepStart))] [NotifyPropertyChangedFor(nameof(IsStepOpen))] [NotifyPropertyChangedFor(nameof(IsStepShowSeed))] @@ -183,6 +185,7 @@ public partial class MainWindowViewModel : ViewModelBase [NotifyPropertyChangedFor(nameof(IsStepPassword))] private string setupStep = StepStart; + public bool IsStepDataLocation => SetupStep == StepDataLocation; public bool IsStepStart => SetupStep == StepStart; public bool IsStepOpen => SetupStep == StepOpen; public bool IsStepShowSeed => SetupStep == StepShowSeed; @@ -191,6 +194,9 @@ public partial class MainWindowViewModel : ViewModelBase public bool IsStepPassphrase => SetupStep == StepPassphrase; public bool IsStepPassword => SetupStep == StepPassword; + /// Percorso dati predefinito, mostrato al primo avvio. + public string DefaultDataPath => AppPaths.DefaultDataRoot(); + /// True quando il flusso è "ripristina" (parole inserite dall'utente). private bool _isRestoreFlow; @@ -375,7 +381,12 @@ public partial class MainWindowViewModel : ViewModelBase public MainWindowViewModel() { _loc = Loc.SwitchTo(_config.Language); - RefreshSetupState(); + // Primo avvio: se la posizione dei dati non è ancora determinata, + // chiediamola prima di tutto il resto del wizard. + if (!AppPaths.IsDataLocationConfigured()) + SetupStep = StepDataLocation; + else + RefreshSetupState(); // Aggiornamenti continui (§9): ping periodico per tenere viva la // connessione e accorgersi subito della caduta; se cade, riconnette // e risincronizza da solo. Le notifiche push restano la via principale. @@ -413,6 +424,34 @@ public partial class MainWindowViewModel : ViewModelBase private ServerRegistry Registry => new(Profile, AppPaths.ServersPath(Net)); + /// Primo avvio: usa il percorso dati predefinito di piattaforma. + [RelayCommand] + private void UseDefaultDataLocation() => ApplyDataLocation(AppPaths.DefaultDataRoot()); + + /// + /// Memorizza la radice dati scelta (default o personalizzata), ricarica la + /// configurazione dalla nuova posizione e prosegue col wizard. Chiamata anche + /// dalla View dopo la scelta di una cartella. + /// + public void ApplyDataLocation(string root) + { + try + { + AppPaths.ConfigureDataLocation(root); + } + catch (Exception ex) + { + StatusMessage = $"{Loc.Tr("msg.error")}: {ex.Message}"; + return; + } + // La config globale vive nella radice dati: ricaricala da lì. + _config = AppConfig.Load(); + _loc = Loc.SwitchTo(_config.Language); + OnPropertyChanged(nameof(Loc)); + OnPropertyChanged(nameof(UnitLabel)); + RefreshSetupState(); + } + private void RefreshSetupState() { SetupStep = StepStart; diff --git a/src/App/Views/MainWindow.axaml b/src/App/Views/MainWindow.axaml index a9d82ee..91da29a 100644 --- a/src/App/Views/MainWindow.axaml +++ b/src/App/Views/MainWindow.axaml @@ -41,6 +41,23 @@ + + + + + + + + +