docs: translate all code comments to English (language policy)
Translate every Italian /// XML doc comment, <!-- --> XAML comment, and // inline comment to English across all source files (Core, App, tests). Add the language policy to CLAUDE.md (conversation Italian; all code and docs English). Update one test assertion that checked an Italian exception message that was also translated.
This commit is contained in:
+3
-3
@@ -9,8 +9,8 @@
|
||||
<local:ViewLocator/>
|
||||
</Application.DataTemplates>
|
||||
|
||||
<!-- Design token semantici (colori, raggi, accento brand): definiti per
|
||||
entrambe le varianti chiaro/scuro. Vedi Styles/Theme.axaml. -->
|
||||
<!-- Semantic design tokens (colors, radii, brand accent): defined for
|
||||
both light/dark variants. See Styles/Theme.axaml. -->
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
<!-- Rifiniture di controllo e classi riutilizzabili (sopra a Fluent) -->
|
||||
<!-- Control refinements and reusable classes (on top of Fluent) -->
|
||||
<StyleInclude Source="/Styles/Controls.axaml"/>
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
@@ -17,8 +17,8 @@ public partial class App : Application
|
||||
{
|
||||
var vm = new MainWindowViewModel();
|
||||
|
||||
// Desktop (Windows/Linux): finestra classica. Mobile (Android): vista
|
||||
// singola. Stessa UI condivisa (MainView) e stesso ViewModel.
|
||||
// Desktop (Windows/Linux): classic window. Mobile (Android): single
|
||||
// view. Same shared UI (MainView) and same ViewModel.
|
||||
switch (ApplicationLifetime)
|
||||
{
|
||||
case IClassicDesktopStyleApplicationLifetime desktop:
|
||||
|
||||
+15
-15
@@ -3,9 +3,9 @@ using System.Collections.Generic;
|
||||
namespace PalladiumWallet.App.Localization;
|
||||
|
||||
/// <summary>
|
||||
/// Localizzazione UI: dizionario chiave → traduzioni per lingua, con
|
||||
/// indicizzatore bindabile da XAML ({Binding Loc[chiave]}). Al cambio lingua
|
||||
/// il ViewModel sostituisce l'istanza così Avalonia rivaluta tutte le binding.
|
||||
/// UI localisation: key → per-language translations dictionary, with an
|
||||
/// indexer bindable from XAML ({Binding Loc[key]}). On language change the
|
||||
/// ViewModel replaces the instance so Avalonia re-evaluates all bindings.
|
||||
/// </summary>
|
||||
public sealed class Loc
|
||||
{
|
||||
@@ -20,10 +20,10 @@ public sealed class Loc
|
||||
private Loc(string language) { Language = language; }
|
||||
|
||||
/// <summary>
|
||||
/// Crea una nuova istanza con la lingua specificata e aggiorna il singleton
|
||||
/// usato da <see cref="Tr"/>. Il ViewModel assegna questa istanza alla
|
||||
/// propria property Loc così Avalonia vede un riferimento diverso e
|
||||
/// rivaluta tutte le binding {Binding Loc[chiave]}.
|
||||
/// Creates a new instance for the specified language and updates the singleton
|
||||
/// used by <see cref="Tr"/>. The ViewModel assigns this instance to its Loc
|
||||
/// property so Avalonia sees a different reference and re-evaluates all
|
||||
/// {Binding Loc[key]} bindings.
|
||||
/// </summary>
|
||||
internal static Loc SwitchTo(string language)
|
||||
{
|
||||
@@ -146,13 +146,13 @@ public sealed class Loc
|
||||
"Fügen Sie einen oder mehrere WIF-Schlüssel ein (einer pro Zeile). Sie können mehrere Schlüssel importieren, um mehrere Adressen mit demselben Wallet zu verwalten."],
|
||||
["wiz.importwif.placeholder"] = ["K… / L… / 5… (una chiave per riga)", "K… / L… / 5… (one key per line)", "K… / L… / 5… (una clave por línea)", "K… / L… / 5… (une clé par ligne)", "K… / L… / 5… (uma chave por linha)", "K… / L… / 5… (ein Schlüssel pro Zeile)"],
|
||||
|
||||
// Messaggi di errore import
|
||||
// Import error messages
|
||||
["msg.xkey.required"] = ["Incolla una chiave estesa (xpub/xprv o variante).", "Paste an extended key (xpub/xprv or variant).", "Pega una clave extendida (xpub/xprv o variante).", "Collez une clé étendue (xpub/xprv ou variante).", "Cole uma chave estendida (xpub/xprv ou variante).", "Fügen Sie einen erweiterten Schlüssel ein (xpub/xprv oder Variante)."],
|
||||
["msg.xkey.invalid"] = ["Chiave estesa non riconosciuta per questa rete.", "Extended key not recognised for this network.", "Clave extendida no reconocida para esta red.", "Clé étendue non reconnue pour ce réseau.", "Chave estendida não reconhecida para esta rede.", "Erweiterter Schlüssel für dieses Netzwerk nicht erkannt."],
|
||||
["msg.wif.required"] = ["Incolla almeno una chiave WIF.", "Paste at least one WIF key.", "Pega al menos una clave WIF.", "Collez au moins une clé WIF.", "Cole pelo menos uma chave WIF.", "Fügen Sie mindestens einen WIF-Schlüssel ein."],
|
||||
["msg.wif.invalid"] = ["Chiave WIF non valida per questa rete.", "WIF key not valid for this network.", "Clave WIF no válida para esta red.", "Clé WIF invalide pour ce réseau.", "Chave WIF inválida para esta rede.", "WIF-Schlüssel für dieses Netzwerk ungültig."],
|
||||
|
||||
// Pannello wallet
|
||||
// Wallet panel
|
||||
["wallet.close"] = ["Chiudi wallet", "Close wallet", "Cerrar wallet", "Fermer le wallet", "Fechar carteira", "Wallet schließen"],
|
||||
["wallet.server"] = ["Server:", "Server:", "Servidor:", "Serveur :", "Servidor:", "Server:"],
|
||||
["wallet.connect"] = ["Connetti e sincronizza", "Connect and sync", "Conectar y sincronizar", "Connecter et synchroniser", "Conectar e sincronizar", "Verbinden und synchronisieren"],
|
||||
@@ -188,7 +188,7 @@ public sealed class Loc
|
||||
["addr.info.title"] = ["Informazioni indirizzo", "Address information", "Información de dirección", "Informations sur l'adresse", "Informações do endereço", "Adressinformationen"],
|
||||
["addr.close"] = ["Chiudi", "Close", "Cerrar", "Fermer", "Fechar", "Schließen"],
|
||||
|
||||
// Storico → dettaglio transazione
|
||||
// History → transaction detail
|
||||
["history.hint"] = ["Doppio click su una transazione per i dettagli.", "Double-click a transaction for details.", "Doble clic en una transacción para ver los detalles.", "Double-cliquez sur une transaction pour les détails.", "Clique duplo numa transação para ver os detalhes.", "Doppelklick auf eine Transaktion für Details."],
|
||||
["tx.title"] = ["Dettagli transazione", "Transaction details", "Detalles de la transacción", "Détails de la transaction", "Detalhes da transação", "Transaktionsdetails"],
|
||||
["tx.close"] = ["Chiudi", "Close", "Cerrar", "Fermer", "Fechar", "Schließen"],
|
||||
@@ -231,7 +231,7 @@ public sealed class Loc
|
||||
["send.scan"] = ["Scansiona QR", "Scan QR", "Escanear QR", "Scanner QR", "Escanear QR", "QR scannen"],
|
||||
["send.confirm"] = ["CONFERMA E TRASMETTI", "CONFIRM AND BROADCAST", "CONFIRMAR Y TRANSMITIR", "CONFIRMER ET DIFFUSER", "CONFIRMAR E TRANSMITIR", "BESTÄTIGEN UND SENDEN"],
|
||||
|
||||
// Stato connessione
|
||||
// Connection status
|
||||
["conn.none"] = ["non connesso", "not connected", "no conectado", "non connecté", "não conectado", "nicht verbunden"],
|
||||
["conn.disconnected"] = ["disconnesso", "disconnected", "desconectado", "déconnecté", "desconectado", "getrennt"],
|
||||
["conn.reconnecting"] = ["riconnessione…", "reconnecting…", "reconectando…", "reconnexion…", "reconectando…", "Verbindung wird wiederhergestellt…"],
|
||||
@@ -240,7 +240,7 @@ public sealed class Loc
|
||||
["conn.connectedto"] = ["connesso", "connected", "conectado", "connecté", "conectado", "verbunden"],
|
||||
["conn.connectingto"] = ["connessione a", "connecting to", "conectando a", "connexion à", "conectando a", "Verbindung zu"],
|
||||
|
||||
// Messaggi di stato principali
|
||||
// Main status messages
|
||||
["msg.welcome.existing"] = [
|
||||
"Trovato un wallet esistente su questa rete: aprilo, oppure creane un altro.",
|
||||
"Found an existing wallet on this network: open it, or create another one.",
|
||||
@@ -351,7 +351,7 @@ public sealed class Loc
|
||||
"SSL-Zertifikate zurückgesetzt: Verbindung erneut versuchen."],
|
||||
["msg.error"] = ["Errore", "Error", "Error", "Erreur", "Erro", "Fehler"],
|
||||
|
||||
// Contatti
|
||||
// Contacts
|
||||
["contacts.name"] = ["Nome", "Name", "Nombre", "Nom", "Nome", "Name"],
|
||||
["contacts.address"] = ["Indirizzo", "Address", "Dirección", "Adresse", "Endereço", "Adresse"],
|
||||
["contacts.name.ph"] = ["Nome contatto", "Contact name", "Nombre del contacto", "Nom du contact", "Nome do contato", "Kontaktname"],
|
||||
@@ -360,7 +360,7 @@ public sealed class Loc
|
||||
["contacts.remove"] = ["Rimuovi selezionato", "Remove selected", "Eliminar seleccionado", "Supprimer la sélection", "Remover selecionado", "Auswahl entfernen"],
|
||||
["contacts.empty"] = ["Nessun contatto salvato.", "No saved contacts.", "No hay contactos guardados.", "Aucun contact enregistré.", "Nenhum contato salvo.", "Keine gespeicherten Kontakte."],
|
||||
|
||||
// Finestra impostazioni
|
||||
// Settings window
|
||||
["settings.title"] = ["Impostazioni", "Settings", "Configuración", "Paramètres", "Configurações", "Einstellungen"],
|
||||
["settings.language"] = ["Lingua", "Language", "Idioma", "Langue", "Idioma", "Sprache"],
|
||||
["settings.unit"] = ["Unità degli importi", "Amount unit", "Unidad de importes", "Unité des montants", "Unidade dos valores", "Betrageinheit"],
|
||||
@@ -368,7 +368,7 @@ public sealed class Loc
|
||||
["settings.cancel"] = ["Annulla", "Cancel", "Cancelar", "Annuler", "Cancelar", "Abbrechen"],
|
||||
["settings.server"] = ["Server di indicizzazione…", "Indexing server…", "Servidor de indexación…", "Serveur d'indexation…", "Servidor de indexação…", "Indexierungsserver…"],
|
||||
|
||||
// Finestra server
|
||||
// Server window
|
||||
["server.title"] = ["Server di indicizzazione", "Indexing server", "Servidor de indexación", "Serveur d'indexation", "Servidor de indexação", "Indexierungsserver"],
|
||||
["server.host"] = ["Host", "Host", "Host", "Hôte", "Host", "Host"],
|
||||
["server.port"] = ["Porta", "Port", "Puerto", "Port", "Porta", "Port"],
|
||||
|
||||
@@ -4,14 +4,14 @@ using System.Threading.Tasks;
|
||||
namespace PalladiumWallet.App.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Seam per servizi specifici della piattaforma (Android/desktop).
|
||||
/// Il head Android imposta i delegate in OnCreate; desktop li lascia null.
|
||||
/// Seam for platform-specific services (Android/desktop).
|
||||
/// The Android head sets the delegates in OnCreate; desktop leaves them null.
|
||||
/// </summary>
|
||||
public static class PlatformServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Apre lo scanner QR nativo e restituisce il testo raw del codice,
|
||||
/// oppure null se l'utente annulla o lo scanner non è disponibile.
|
||||
/// Opens the native QR scanner and returns the raw code text,
|
||||
/// or null if the user cancels or the scanner is unavailable.
|
||||
/// </summary>
|
||||
public static Func<Task<string?>>? ScanQrAsync { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<!-- ============================================================
|
||||
Stili di controllo del wallet Palladium ("fintech pulito").
|
||||
Restano sopra al FluentTheme: rifiniscono spaziatura, stati e
|
||||
classi riutilizzabili senza ritemplare i controlli. I bottoni
|
||||
"accent" restano gestiti da Fluent ma ereditano il blu del brand
|
||||
via SystemAccentColor (vedi Theme.axaml).
|
||||
Palladium wallet control styles ("clean fintech").
|
||||
Applied on top of FluentTheme: refine spacing, states and
|
||||
reusable classes without re-templating controls. The "accent"
|
||||
buttons remain managed by Fluent but inherit the brand blue
|
||||
via SystemAccentColor (see Theme.axaml).
|
||||
============================================================ -->
|
||||
|
||||
<Design.PreviewWith>
|
||||
@@ -22,7 +22,7 @@
|
||||
</Border>
|
||||
</Design.PreviewWith>
|
||||
|
||||
<!-- ===== Tipografia ===== -->
|
||||
<!-- ===== Typography ===== -->
|
||||
<Style Selector="TextBlock.h1">
|
||||
<Setter Property="FontSize" Value="20"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
@@ -36,7 +36,7 @@
|
||||
<Style Selector="TextBlock.muted">
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextSecondaryBrush}"/>
|
||||
</Style>
|
||||
<!-- Importi e dati numerici: cifre tabulari per evitare salti di layout -->
|
||||
<!-- Amounts and numeric data: tabular figures to prevent layout jumps -->
|
||||
<Style Selector="TextBlock.amount">
|
||||
<Setter Property="FontSize" Value="34"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
@@ -47,7 +47,7 @@
|
||||
<Setter Property="FontFamily" Value="monospace"/>
|
||||
<Setter Property="FontFeatures" Value="+tnum"/>
|
||||
</Style>
|
||||
<!-- Intestazione di sezione (es. dettaglio transazione) -->
|
||||
<!-- Section heading (e.g. transaction detail) -->
|
||||
<Style Selector="TextBlock.section">
|
||||
<Setter Property="FontSize" Value="11"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
@@ -55,7 +55,7 @@
|
||||
<Setter Property="LetterSpacing" Value="0.8"/>
|
||||
</Style>
|
||||
|
||||
<!-- ===== Bottoni ===== -->
|
||||
<!-- ===== Buttons ===== -->
|
||||
<Style Selector="Button">
|
||||
<Setter Property="MinHeight" Value="36"/>
|
||||
<Setter Property="Padding" Value="14,8"/>
|
||||
@@ -64,19 +64,19 @@
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<!-- I bottoni accent (CTA) sono già blu via Fluent+SystemAccentColor.
|
||||
Garantiamo testo bianco leggibile in entrambe le varianti. -->
|
||||
<!-- Accent (CTA) buttons are already blue via Fluent+SystemAccentColor.
|
||||
Ensure readable white text in both light and dark variants. -->
|
||||
<Style Selector="Button.accent">
|
||||
<Setter Property="Foreground" Value="#FFFFFF"/>
|
||||
</Style>
|
||||
<!-- Variante "ghost": azione secondaria discreta, senza riempimento -->
|
||||
<!-- "ghost" variant: subtle secondary action, no fill -->
|
||||
<Style Selector="Button.ghost">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource PrimaryBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Style>
|
||||
|
||||
<!-- ===== Input ===== -->
|
||||
<!-- ===== Inputs ===== -->
|
||||
<Style Selector="TextBox">
|
||||
<Setter Property="MinHeight" Value="36"/>
|
||||
<Setter Property="Padding" Value="10,7"/>
|
||||
@@ -86,7 +86,7 @@
|
||||
<Setter Property="Padding" Value="10,6"/>
|
||||
</Style>
|
||||
|
||||
<!-- ===== Liste ===== -->
|
||||
<!-- ===== Lists ===== -->
|
||||
<Style Selector="ListBox">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
@@ -97,7 +97,7 @@
|
||||
<Setter Property="CornerRadius" Value="8"/>
|
||||
<Setter Property="Margin" Value="0,1"/>
|
||||
</Style>
|
||||
<!-- Hover: tinta neutra; selezione: tinta blu tenue del brand -->
|
||||
<!-- Hover: neutral tint; selected: subtle brand-blue tint -->
|
||||
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource SurfaceAltBrush}"/>
|
||||
</Style>
|
||||
@@ -105,8 +105,8 @@
|
||||
<Setter Property="Background" Value="{DynamicResource PrimarySoftBrush}"/>
|
||||
</Style>
|
||||
|
||||
<!-- ===== Tab (dashboard) ===== -->
|
||||
<!-- Voce selezionata in blu brand + grassetto; non selezionata smorzata -->
|
||||
<!-- ===== Tabs (dashboard) ===== -->
|
||||
<!-- Selected item: brand blue + bold; unselected: muted -->
|
||||
<Style Selector="TabItem">
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextSecondaryBrush}"/>
|
||||
<Setter Property="FontWeight" Value="Medium"/>
|
||||
@@ -118,8 +118,8 @@
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource PrimaryBrush}"/>
|
||||
</Style>
|
||||
|
||||
<!-- ===== Classi riutilizzabili ===== -->
|
||||
<!-- Card: superficie elevata con bordo sottile e angoli morbidi -->
|
||||
<!-- ===== Reusable classes ===== -->
|
||||
<!-- Card: elevated surface with a subtle border and soft corners -->
|
||||
<Style Selector="Border.card">
|
||||
<Setter Property="Background" Value="{DynamicResource SurfaceBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource BorderSubtleBrush}"/>
|
||||
@@ -135,7 +135,7 @@
|
||||
<Style Selector="Border.card:pointerover">
|
||||
<Setter Property="BoxShadow" Value="0 6 16 -4 #26000000"/>
|
||||
</Style>
|
||||
<!-- Card del saldo: accento blu sottile, leggermente più marcata -->
|
||||
<!-- Balance card: subtle blue accent, slightly more prominent -->
|
||||
<Style Selector="Border.balance-card">
|
||||
<Setter Property="Background" Value="{DynamicResource SurfaceBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource BorderSubtleBrush}"/>
|
||||
@@ -144,8 +144,8 @@
|
||||
<Setter Property="Padding" Value="20,16"/>
|
||||
<Setter Property="BoxShadow" Value="0 2 8 0 #1A000000"/>
|
||||
</Style>
|
||||
<!-- Card del saldo "hero": gradiente brand + bagliore blu. Elemento
|
||||
distintivo della dashboard; il testo sopra è bianco (classe on-hero). -->
|
||||
<!-- Hero balance card: brand gradient + blue glow. Signature dashboard
|
||||
element; overlaid text is white (on-hero class). -->
|
||||
<Style Selector="Border.balance-hero">
|
||||
<Setter Property="Background" Value="{DynamicResource BalanceGradientBrush}"/>
|
||||
<Setter Property="CornerRadius" Value="16"/>
|
||||
@@ -158,7 +158,7 @@
|
||||
<Style Selector="Border.balance-hero TextBlock.on-hero">
|
||||
<Setter Property="Foreground" Value="#D6E2FF"/>
|
||||
</Style>
|
||||
<!-- Pillola/badge di stato -->
|
||||
<!-- Status pill / badge -->
|
||||
<Style Selector="Border.chip">
|
||||
<Setter Property="CornerRadius" Value="20"/>
|
||||
<Setter Property="Padding" Value="10,5"/>
|
||||
|
||||
+25
-25
@@ -1,34 +1,34 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<!-- ============================================================
|
||||
Design token del wallet Palladium.
|
||||
Identità: blu cobalto (fiducia) + verde (denaro/positivo),
|
||||
derivati dal logo. Stile "fintech pulito".
|
||||
Palladium wallet design tokens.
|
||||
Identity: cobalt blue (trust) + green (money/positive),
|
||||
derived from the logo. "Clean fintech" style.
|
||||
|
||||
Tutti i colori sono SEMANTICI (ruolo, non nome): i componenti
|
||||
non devono mai usare hex inline ma questi brush. Definiti per
|
||||
entrambe le varianti (chiaro/scuro) via ThemeDictionaries, così
|
||||
l'app segue il tema di sistema senza ulteriore configurazione.
|
||||
All colors are SEMANTIC (role, not name): components
|
||||
must never use inline hex but these brushes. Defined for
|
||||
both variants (light/dark) via ThemeDictionaries, so
|
||||
the app follows the system theme with no further configuration.
|
||||
============================================================ -->
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<!-- ============ TEMA CHIARO ============ -->
|
||||
<!-- ============ LIGHT THEME ============ -->
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<!-- Superfici -->
|
||||
<!-- Surfaces -->
|
||||
<SolidColorBrush x:Key="AppBackgroundBrush" Color="#F1F5F9"/> <!-- slate-100 -->
|
||||
<SolidColorBrush x:Key="SurfaceBrush" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="SurfaceAltBrush" Color="#F8FAFC"/> <!-- slate-50 -->
|
||||
<SolidColorBrush x:Key="OverlayCardBrush" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="ScrimBrush" Color="#66000000"/> <!-- 40% -->
|
||||
<!-- Bordi e separatori -->
|
||||
<!-- Borders and separators -->
|
||||
<SolidColorBrush x:Key="BorderSubtleBrush" Color="#E2E8F0"/> <!-- slate-200 -->
|
||||
<SolidColorBrush x:Key="BorderStrongBrush" Color="#CBD5E1"/> <!-- slate-300 -->
|
||||
<!-- Testo -->
|
||||
<!-- Text -->
|
||||
<SolidColorBrush x:Key="TextPrimaryBrush" Color="#0F172A"/> <!-- slate-900 -->
|
||||
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#475569"/> <!-- slate-600 -->
|
||||
<SolidColorBrush x:Key="TextMutedBrush" Color="#94A3B8"/> <!-- slate-400 -->
|
||||
<!-- Brand / stato -->
|
||||
<!-- Brand / state -->
|
||||
<SolidColorBrush x:Key="PrimaryBrush" Color="#2563EB"/> <!-- blue-600 -->
|
||||
<SolidColorBrush x:Key="PrimaryHoverBrush" Color="#1D4ED8"/> <!-- blue-700 -->
|
||||
<SolidColorBrush x:Key="OnPrimaryBrush" Color="#FFFFFF"/>
|
||||
@@ -36,29 +36,29 @@
|
||||
<SolidColorBrush x:Key="SuccessBrush" Color="#16A34A"/> <!-- green-600 -->
|
||||
<SolidColorBrush x:Key="WarningBrush" Color="#D97706"/> <!-- amber-600 -->
|
||||
<SolidColorBrush x:Key="DangerBrush" Color="#DC2626"/> <!-- red-600 -->
|
||||
<!-- Gradiente hero della card saldo: blu → indaco -->
|
||||
<!-- Hero gradient of the balance card: blue → indigo -->
|
||||
<LinearGradientBrush x:Key="BalanceGradientBrush" StartPoint="0%,0%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0" Color="#2563EB"/>
|
||||
<GradientStop Offset="1" Color="#4F46E5"/>
|
||||
</LinearGradientBrush>
|
||||
</ResourceDictionary>
|
||||
|
||||
<!-- ============ TEMA SCURO ============ -->
|
||||
<!-- ============ DARK THEME ============ -->
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<!-- Superfici -->
|
||||
<!-- Surfaces -->
|
||||
<SolidColorBrush x:Key="AppBackgroundBrush" Color="#0F172A"/> <!-- slate-900 -->
|
||||
<SolidColorBrush x:Key="SurfaceBrush" Color="#1E293B"/> <!-- slate-800 -->
|
||||
<SolidColorBrush x:Key="SurfaceAltBrush" Color="#273449"/>
|
||||
<SolidColorBrush x:Key="OverlayCardBrush" Color="#1E293B"/>
|
||||
<SolidColorBrush x:Key="ScrimBrush" Color="#99000000"/> <!-- 60% -->
|
||||
<!-- Bordi e separatori -->
|
||||
<!-- Borders and separators -->
|
||||
<SolidColorBrush x:Key="BorderSubtleBrush" Color="#334155"/> <!-- slate-700 -->
|
||||
<SolidColorBrush x:Key="BorderStrongBrush" Color="#475569"/> <!-- slate-600 -->
|
||||
<!-- Testo -->
|
||||
<!-- Text -->
|
||||
<SolidColorBrush x:Key="TextPrimaryBrush" Color="#F8FAFC"/> <!-- slate-50 -->
|
||||
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#94A3B8"/> <!-- slate-400 -->
|
||||
<SolidColorBrush x:Key="TextMutedBrush" Color="#64748B"/> <!-- slate-500 -->
|
||||
<!-- Brand / stato (toni più luminosi su fondo scuro) -->
|
||||
<!-- Brand / state (brighter tones on dark background) -->
|
||||
<SolidColorBrush x:Key="PrimaryBrush" Color="#3B82F6"/> <!-- blue-500 -->
|
||||
<SolidColorBrush x:Key="PrimaryHoverBrush" Color="#60A5FA"/> <!-- blue-400 -->
|
||||
<SolidColorBrush x:Key="OnPrimaryBrush" Color="#0B1120"/>
|
||||
@@ -66,7 +66,7 @@
|
||||
<SolidColorBrush x:Key="SuccessBrush" Color="#22C55E"/> <!-- green-500 -->
|
||||
<SolidColorBrush x:Key="WarningBrush" Color="#F59E0B"/> <!-- amber-500 -->
|
||||
<SolidColorBrush x:Key="DangerBrush" Color="#EF4444"/> <!-- red-500 -->
|
||||
<!-- Gradiente hero della card saldo: blu → indaco (toni più profondi) -->
|
||||
<!-- Hero gradient of the balance card: blue → indigo (deeper tones) -->
|
||||
<LinearGradientBrush x:Key="BalanceGradientBrush" StartPoint="0%,0%" EndPoint="100%,100%">
|
||||
<GradientStop Offset="0" Color="#1D4ED8"/>
|
||||
<GradientStop Offset="1" Color="#4338CA"/>
|
||||
@@ -76,10 +76,10 @@
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<!-- ============================================================
|
||||
Override dell'accento Fluent → blu del brand. Recolora in modo
|
||||
coerente i controlli che leggono SystemAccentColor (bottoni
|
||||
accent, selezioni, indicatore tab, focus) senza ritemplare nulla.
|
||||
Indipendente dalla variante: #2563EB funziona su chiaro e scuro.
|
||||
Override of the Fluent accent → brand blue. Recolors
|
||||
consistently the controls that read SystemAccentColor (accent
|
||||
buttons, selections, tab indicator, focus) without retemplating anything.
|
||||
Variant-independent: #2563EB works on light and dark.
|
||||
============================================================ -->
|
||||
<Color x:Key="SystemAccentColor">#2563EB</Color>
|
||||
<Color x:Key="SystemAccentColorDark1">#1D4ED8</Color>
|
||||
@@ -89,8 +89,8 @@
|
||||
<Color x:Key="SystemAccentColorLight2">#60A5FA</Color>
|
||||
<Color x:Key="SystemAccentColorLight3">#93C5FD</Color>
|
||||
|
||||
<!-- Raggi globali dei controlli Fluent: arrotonda bottoni, input, combo,
|
||||
liste in modo uniforme senza dover ritemplare ogni controllo. -->
|
||||
<!-- Global Fluent control corner radii: rounds buttons, inputs, combos,
|
||||
and lists uniformly without retemplating each control. -->
|
||||
<CornerRadius x:Key="ControlCornerRadius">8</CornerRadius>
|
||||
<CornerRadius x:Key="OverlayCornerRadius">12</CornerRadius>
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -6,8 +6,8 @@ using Avalonia.Data.Converters;
|
||||
namespace PalladiumWallet.App.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// true (mobile) → Dock.Bottom — tab strip in basso, standard Android.
|
||||
/// false (desktop) → Dock.Top — comportamento predefinito Avalonia.
|
||||
/// true (mobile) → Dock.Bottom — tab strip at the bottom, Android standard.
|
||||
/// false (desktop) → Dock.Top — Avalonia default behavior.
|
||||
/// </summary>
|
||||
public sealed class BoolToTabPlacementConverter : IValueConverter
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ public partial class MainWindowViewModel
|
||||
[ObservableProperty]
|
||||
private ContactEntry? selectedContactInList;
|
||||
|
||||
/// <summary>Contatto selezionato nella ComboBox del pannello Invia: riempie SendTo.</summary>
|
||||
/// <summary>Contact selected in the Send panel's ComboBox: fills SendTo.</summary>
|
||||
[ObservableProperty]
|
||||
private ContactEntry? sendToContact;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace PalladiumWallet.App.ViewModels;
|
||||
|
||||
public partial class MainWindowViewModel
|
||||
{
|
||||
// ---- saldo e info wallet ----
|
||||
// ---- balance and wallet info ----
|
||||
|
||||
[ObservableProperty]
|
||||
private string balanceText = "—";
|
||||
@@ -28,7 +28,7 @@ public partial class MainWindowViewModel
|
||||
[ObservableProperty]
|
||||
private string networkInfo = "";
|
||||
|
||||
// ---- indirizzo di ricezione e QR ----
|
||||
// ---- receive address and QR ----
|
||||
|
||||
[ObservableProperty]
|
||||
private string receiveAddress = "";
|
||||
@@ -60,7 +60,7 @@ public partial class MainWindowViewModel
|
||||
}
|
||||
}
|
||||
|
||||
// ---- tab indirizzi ----
|
||||
// ---- addresses tab ----
|
||||
|
||||
[ObservableProperty]
|
||||
private AddressRow? selectedAddressRow;
|
||||
@@ -74,7 +74,7 @@ public partial class MainWindowViewModel
|
||||
[RelayCommand]
|
||||
private void CloseAddressInfo() => AddressInfo = null;
|
||||
|
||||
// ---- overlay dettaglio transazione ----
|
||||
// ---- transaction detail overlay ----
|
||||
|
||||
[ObservableProperty]
|
||||
private bool isTxDetailsOpen;
|
||||
@@ -169,7 +169,7 @@ public partial class MainWindowViewModel
|
||||
}
|
||||
}
|
||||
|
||||
// ---- aggiorna display dal risultato della sync ----
|
||||
// ---- update display from the sync result ----
|
||||
|
||||
private void ApplyCache(SyncCache? cache)
|
||||
{
|
||||
|
||||
@@ -36,7 +36,7 @@ public partial class MainWindowViewModel
|
||||
if (PlatformServices.ScanQrAsync is not { } scan) return;
|
||||
var raw = await scan();
|
||||
if (string.IsNullOrWhiteSpace(raw)) return;
|
||||
// Gestisce URI tipo "palladium:ADDRESS?amount=X" estraendo solo l'indirizzo
|
||||
// Handle URIs like "palladium:ADDRESS?amount=X" — extract address only
|
||||
var address = raw.Contains(':') ? raw.Split(':')[1] : raw;
|
||||
if (address.Contains('?')) address = address.Split('?')[0];
|
||||
SendTo = address.Trim();
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace PalladiumWallet.App.ViewModels;
|
||||
|
||||
public partial class MainWindowViewModel
|
||||
{
|
||||
// ---- server e connessione ----
|
||||
// ---- server and connection ----
|
||||
|
||||
[ObservableProperty]
|
||||
private string serverHost = "";
|
||||
@@ -118,20 +118,20 @@ public partial class MainWindowViewModel
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restituisce i server da provare in ordine: prima quello selezionato nella UI
|
||||
/// (o digitato manualmente), poi gli altri in KnownServers, evitando duplicati.
|
||||
/// Returns servers to try in order: first the one selected in the UI
|
||||
/// (or manually typed), then the remaining KnownServers, with duplicates skipped.
|
||||
/// </summary>
|
||||
private IEnumerable<(string Host, int Port)> BuildServerCandidates(string selectedHost, int selectedPort)
|
||||
{
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
// 1. Server corrente (selezionato o digitato manualmente).
|
||||
// 1. Current server (selected or manually typed).
|
||||
if (!string.IsNullOrWhiteSpace(selectedHost))
|
||||
{
|
||||
var key = $"{selectedHost}:{selectedPort}";
|
||||
if (seen.Add(key))
|
||||
yield return (selectedHost, selectedPort);
|
||||
}
|
||||
// 2. Altri server noti, in ordine di lista.
|
||||
// 2. Other known servers, in list order.
|
||||
foreach (var s in KnownServers)
|
||||
{
|
||||
var p = s.PortFor(UseSsl);
|
||||
@@ -163,14 +163,14 @@ public partial class MainWindowViewModel
|
||||
|
||||
if (_client is null || !_client.IsConnected)
|
||||
{
|
||||
// Salva le cache prima di distruggere il synchronizer: il nuovo
|
||||
// synchronizer userà un client diverso e deve essere ricreato,
|
||||
// ma i dati già scaricati vengono preservati via _doc.Cache.
|
||||
// Persist caches before destroying the synchronizer: the new
|
||||
// synchronizer will use a different client and must be recreated,
|
||||
// but already-downloaded data is preserved via _doc.Cache.
|
||||
PersistPartialTxCache();
|
||||
_synchronizer = null;
|
||||
|
||||
// Prova tutti i server noti in ordine; parte da quello selezionato
|
||||
// e scorre la lista fino al primo che risponde.
|
||||
// Try all known servers in order; starts with the selected one
|
||||
// and walks the list until the first that responds.
|
||||
var candidates = BuildServerCandidates(host, port);
|
||||
Exception? lastError = null;
|
||||
foreach (var (h, p) in candidates)
|
||||
@@ -189,7 +189,7 @@ public partial class MainWindowViewModel
|
||||
IsConnected = true;
|
||||
_autoReconnect = true;
|
||||
ConnectionStatus = Loc.Tr("conn.connectedto");
|
||||
// Aggiorna la UI per riflettere il server effettivamente connesso.
|
||||
// Update the UI to reflect the server actually connected.
|
||||
_syncingServerFields = true;
|
||||
ServerHost = h;
|
||||
ServerPort = p.ToString();
|
||||
@@ -215,8 +215,8 @@ public partial class MainWindowViewModel
|
||||
if (_synchronizer is null)
|
||||
{
|
||||
_synchronizer = new WalletSynchronizer(_account, _client!, _doc.GapLimit);
|
||||
// Ricarica dalla cache su disco: evita di riscaricale le tx già note
|
||||
// (fondamentale per wallet con migliaia di tx storiche — previene -101).
|
||||
// Reload from disk cache: avoids re-downloading already known transactions
|
||||
// (essential for wallets with thousands of historical txs — prevents -101).
|
||||
var net = PalladiumNetworks.For(_account.Profile.Kind);
|
||||
_synchronizer.PreloadCaches(
|
||||
_doc.Cache?.RawTxHex ?? [],
|
||||
@@ -271,8 +271,8 @@ public partial class MainWindowViewModel
|
||||
if (_account is not null)
|
||||
{
|
||||
_syncFailed = true;
|
||||
// Salva le tx già scaricate: al retry il synchronizer riparte
|
||||
// da qui invece di ricominciare da zero (es. dopo -101).
|
||||
// Persist already-downloaded transactions: on retry the synchronizer
|
||||
// resumes from here instead of starting from scratch (e.g. after -101).
|
||||
PersistPartialTxCache();
|
||||
}
|
||||
}
|
||||
@@ -327,9 +327,9 @@ public partial class MainWindowViewModel
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Salva nel SyncCache le tx e le prove di Merkle già accumulate dal synchronizer,
|
||||
/// anche se la sync non è ancora completa. Consente al retry successivo
|
||||
/// (o al riavvio dell'app) di riprendere senza riscaricale da zero.
|
||||
/// Persists the transactions and Merkle proofs already accumulated by the synchronizer
|
||||
/// into SyncCache, even if sync is not yet complete. Allows the next retry
|
||||
/// (or app restart) to resume without re-downloading from scratch.
|
||||
/// </summary>
|
||||
private void PersistPartialTxCache()
|
||||
{
|
||||
@@ -346,7 +346,7 @@ public partial class MainWindowViewModel
|
||||
_doc.Cache.BlockHeaders = blockHeaders;
|
||||
WalletStore.Save(_doc, _walletPath, _password);
|
||||
}
|
||||
catch { /* non fatale: il prossimo salvataggio completo recupererà */ }
|
||||
catch { /* non-fatal: the next full save will recover */ }
|
||||
}
|
||||
|
||||
private async Task DisconnectAsync()
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace PalladiumWallet.App.ViewModels;
|
||||
|
||||
public partial class MainWindowViewModel
|
||||
{
|
||||
// ---- wizard di setup
|
||||
// ---- setup wizard
|
||||
|
||||
public const string StepDataLocation = "data-location";
|
||||
public const string StepStart = "start";
|
||||
@@ -85,7 +85,7 @@ public partial class MainWindowViewModel
|
||||
[ObservableProperty]
|
||||
private string importWifInput = "";
|
||||
|
||||
// Tipo di script rilevato durante la decodifica dell'xkey (per mostrarlo all'utente)
|
||||
// Script type detected during xkey decoding (to display to the user)
|
||||
[ObservableProperty]
|
||||
private string importXkeyDetectedKind = "";
|
||||
|
||||
@@ -268,7 +268,7 @@ public partial class MainWindowViewModel
|
||||
StatusMessage = Loc.Tr("msg.xkey.required");
|
||||
return;
|
||||
}
|
||||
// Valida e rileva il tipo: prova prima come xpub, poi come xprv.
|
||||
// Validate and detect the type: try as xpub first, then as xprv.
|
||||
if (Slip132.TryDecodePublic(ImportXkeyInput.Trim(), Profile, out _, out var pubKind))
|
||||
{
|
||||
SelectedScriptKind = pubKind;
|
||||
@@ -298,7 +298,7 @@ public partial class MainWindowViewModel
|
||||
StatusMessage = Loc.Tr("msg.wif.required");
|
||||
return;
|
||||
}
|
||||
// Valida il WIF con un parsing anticipato.
|
||||
// Validate the WIF with an early parse.
|
||||
try
|
||||
{
|
||||
_ = new NBitcoin.BitcoinSecret(ImportWifInput.Trim(), PalladiumNetworks.For(Net));
|
||||
@@ -421,9 +421,9 @@ public partial class MainWindowViewModel
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Costruisce il percorso file dal nome inserito dall'utente.
|
||||
/// Se il nome è vuoto genera un nome automatico (default, wallet-2, …).
|
||||
/// Rimuove i caratteri non validi per il filesystem.
|
||||
/// Builds the file path from the name entered by the user.
|
||||
/// If the name is empty, generates an automatic name (default, wallet-2, …).
|
||||
/// Removes characters that are invalid for the filesystem.
|
||||
/// </summary>
|
||||
private string WalletPathFromName(string name)
|
||||
{
|
||||
@@ -435,7 +435,7 @@ public partial class MainWindowViewModel
|
||||
|
||||
if (string.IsNullOrEmpty(clean))
|
||||
{
|
||||
// Nessun nome → nome automatico
|
||||
// No name provided → auto-generated name
|
||||
var def = AppPaths.DefaultWalletPath(Net);
|
||||
for (var n = 2; WalletStore.Exists(def); n++)
|
||||
def = Path.Combine(AppPaths.WalletsDir(Net), $"wallet-{n}.wallet.json");
|
||||
@@ -547,8 +547,8 @@ public partial class MainWindowViewModel
|
||||
ApplyCache(doc.Cache);
|
||||
IsWalletOpen = true;
|
||||
StatusMessage = Loc.Tr("msg.opened");
|
||||
_autoReconnect = true; // keepalive riprova la connessione anche se il primo tentativo fallisce
|
||||
_syncFailed = true; // forza la prima sync automatica
|
||||
_autoReconnect = true; // keepalive retries the connection even if the first attempt fails
|
||||
_syncFailed = true; // force the first automatic sync
|
||||
_ = ConnectAndSync();
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,10 @@ using PalladiumWallet.Core.Wallet;
|
||||
|
||||
namespace PalladiumWallet.App.ViewModels;
|
||||
|
||||
/// <summary>Riga dello storico transazioni per la vista.</summary>
|
||||
/// <summary>Transaction history row for the view.</summary>
|
||||
public sealed record HistoryRow(string Conferma, string Importo, string Txid, string Verificata);
|
||||
|
||||
/// <summary>Riga della vista indirizzi con chiavi e derivation path pre-calcolati.</summary>
|
||||
/// <summary>Address view row with pre-computed keys and derivation path.</summary>
|
||||
public sealed record AddressRow(
|
||||
string Tipo, int Indice, string Indirizzo, string Saldo, string NumTx,
|
||||
bool IsChange = false, string PubKey = "", string PrivKey = "", string DerivPath = "")
|
||||
@@ -27,7 +27,7 @@ public sealed record AddressRow(
|
||||
public bool HasPrivKey => !string.IsNullOrEmpty(PrivKey);
|
||||
}
|
||||
|
||||
/// <summary>Dati completi di un indirizzo passati alla finestra di dettaglio.</summary>
|
||||
/// <summary>Full address data passed to the address detail overlay.</summary>
|
||||
public sealed record AddressInfo(
|
||||
Loc Loc,
|
||||
string Address, string DerivPath, string PubKey, string PrivKey)
|
||||
@@ -35,38 +35,38 @@ public sealed record AddressInfo(
|
||||
public bool HasPrivKey => !string.IsNullOrEmpty(PrivKey);
|
||||
}
|
||||
|
||||
/// <summary>Contatto in rubrica: nome + indirizzo blockchain.</summary>
|
||||
/// <summary>Address book contact: name + blockchain address.</summary>
|
||||
public sealed record ContactEntry(string Name, string Address);
|
||||
|
||||
/// <summary>Voce della lista di scelta wallet: nome file + percorso completo.</summary>
|
||||
/// <summary>Wallet list entry: display name + full file path.</summary>
|
||||
public sealed record WalletFileEntry(string Name, string Path);
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel unico dell'applicazione (wizard + dashboard). Suddiviso in file
|
||||
/// partial per area: Wizard, Settings, Sync, Send, Contacts, Receive.
|
||||
/// Single application ViewModel (wizard + dashboard). Split into partial files
|
||||
/// by area: Wizard, Settings, Sync, Send, Contacts, Receive.
|
||||
/// </summary>
|
||||
public partial class MainWindowViewModel : ViewModelBase
|
||||
{
|
||||
// ---- stato sessione wallet ----
|
||||
// ---- wallet session state ----
|
||||
private WalletDocument? _doc;
|
||||
private IWalletAccount? _account;
|
||||
private string? _walletPath;
|
||||
private string? _password;
|
||||
private WalletLock? _walletLock;
|
||||
|
||||
// ---- rete ----
|
||||
// ---- network ----
|
||||
private ElectrumClient? _client;
|
||||
private WalletSynchronizer? _synchronizer;
|
||||
private IReadOnlyDictionary<string, Transaction>? _lastTransactions;
|
||||
private bool _resyncRequested;
|
||||
|
||||
// ---- invio ----
|
||||
// ---- send ----
|
||||
private BuiltTransaction? _pendingSend;
|
||||
|
||||
// ---- dettaglio transazione ----
|
||||
// ---- transaction detail ----
|
||||
private CancellationTokenSource? _txDetailsCts;
|
||||
|
||||
// ---- configurazione e localizzazione ----
|
||||
// ---- configuration and localisation ----
|
||||
private AppConfig _config = AppConfig.Load();
|
||||
private Loc _loc = Loc.Instance;
|
||||
public Loc Loc => _loc;
|
||||
@@ -82,7 +82,7 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
// ---- server UI sync ----
|
||||
private bool _syncingServerFields;
|
||||
|
||||
// ---- proprietà di app ----
|
||||
// ---- app properties ----
|
||||
|
||||
public static string AppVersion =>
|
||||
typeof(MainWindowViewModel).Assembly.GetName().Version is { } v
|
||||
@@ -91,7 +91,7 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
|
||||
public string WindowTitle => $"Palladium Wallet {AppVersion}";
|
||||
|
||||
/// <summary>true su desktop; false su Android/iOS — nasconde le funzioni legate al filesystem libero.</summary>
|
||||
/// <summary>True on desktop; false on Android/iOS — hides filesystem-dependent features.</summary>
|
||||
public bool IsDesktop => !OperatingSystem.IsAndroid() && !OperatingSystem.IsIOS();
|
||||
|
||||
public bool IsMobile => !IsDesktop;
|
||||
@@ -99,7 +99,7 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
public string UnitLabel => _config.Unit;
|
||||
public AppConfig CurrentConfig => _config;
|
||||
|
||||
// ---- stato pannelli ----
|
||||
// ---- panel state ----
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsSetupVisible))]
|
||||
@@ -116,7 +116,7 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
[RelayCommand]
|
||||
private void SelectTab(string index) => SelectedTabIndex = int.Parse(index);
|
||||
|
||||
// ---- collections per la dashboard ----
|
||||
// ---- dashboard collections ----
|
||||
|
||||
public ObservableCollection<HistoryRow> History { get; } = [];
|
||||
public ObservableCollection<AddressRow> Addresses { get; } = [];
|
||||
@@ -141,7 +141,7 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
catch { return ""; }
|
||||
}
|
||||
|
||||
// ---- costruttore ----
|
||||
// ---- constructor ----
|
||||
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
@@ -161,7 +161,7 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
return;
|
||||
if (_client is { IsConnected: true })
|
||||
{
|
||||
// Se il wallet è aperto e l'ultima sync è fallita, riprova automaticamente.
|
||||
// If the wallet is open and the last sync failed, retry automatically.
|
||||
if (_syncFailed && _account is not null)
|
||||
{
|
||||
await ConnectAndSync();
|
||||
@@ -177,7 +177,7 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
// ---- ciclo di vita wallet ----
|
||||
// ---- wallet lifecycle ----
|
||||
|
||||
[RelayCommand]
|
||||
private void CloseWallet()
|
||||
|
||||
@@ -7,8 +7,8 @@ using Avalonia.Media;
|
||||
namespace PalladiumWallet.App.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// bool → pennello: gli indirizzi del wallet (input/output "nostri") sono
|
||||
/// evidenziati in verde, gli altri usano il colore di testo predefinito.
|
||||
/// bool → brush: the wallet's own addresses ("our" inputs/outputs) are
|
||||
/// highlighted in green, the others use the default text color.
|
||||
/// </summary>
|
||||
public sealed class MineColorConverter : IValueConverter
|
||||
{
|
||||
|
||||
@@ -7,13 +7,13 @@ using PalladiumWallet.Core.Wallet;
|
||||
|
||||
namespace PalladiumWallet.App.ViewModels;
|
||||
|
||||
/// <summary>Riga input/output per le tabelle della finestra di dettaglio.</summary>
|
||||
/// <summary>Input/output row for the transaction detail tables.</summary>
|
||||
public sealed record TxIoRow(string Position, string Address, string Amount, bool IsMine);
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel della finestra di dettaglio transazione: prende un
|
||||
/// <see cref="TransactionDetails"/> (assemblato dal server) e ne ricava tutte
|
||||
/// le stringhe già formattate e localizzate per la vista. È di sola lettura.
|
||||
/// ViewModel for the transaction detail view: takes a
|
||||
/// <see cref="TransactionDetails"/> (assembled from server data) and produces
|
||||
/// all strings already formatted and localised for the view. Read-only.
|
||||
/// </summary>
|
||||
public sealed class TransactionDetailsViewModel
|
||||
{
|
||||
@@ -35,14 +35,14 @@ public sealed class TransactionDetailsViewModel
|
||||
|
||||
var counterparties = d.CounterpartyAddresses;
|
||||
CounterpartyHeader = d.IsIncoming ? loc["tx.from"] : loc["tx.to"];
|
||||
// Una coinbase non ha mittente: le monete sono generate dal mining.
|
||||
// Coinbase has no sender: coins are newly generated by mining.
|
||||
CounterpartyText = d.IsCoinbase
|
||||
? loc["tx.coinbase.newcoins"]
|
||||
: counterparties.Count > 0
|
||||
? string.Join(Environment.NewLine, counterparties)
|
||||
: "—";
|
||||
|
||||
// Debito (uscita verso terzi) o Credito (entrata netta sui nostri output).
|
||||
// Debit (outflow to third parties) or Credit (net inflow on our outputs).
|
||||
AmountHeader = d.IsIncoming ? loc["tx.credit"] : loc["tx.debit"];
|
||||
AmountText = d.IsIncoming
|
||||
? Signed(d.ReceivedSats)
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
</Design.DataContext>
|
||||
|
||||
<UserControl.Styles>
|
||||
<!-- Su desktop gli overlay card sono centrati con dimensione massima;
|
||||
su mobile il default (stretch, nessun margine) occupa tutta la schermata. -->
|
||||
<!-- On desktop, overlay cards are centred with a max size;
|
||||
on mobile the default (stretch, no margin) fills the whole screen. -->
|
||||
<Style Selector="Border.desktop-overlay">
|
||||
<Setter Property="MaxWidth" Value="540"/>
|
||||
<Setter Property="MaxHeight" Value="540"/>
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
<Grid RowDefinitions="Auto,*,Auto" Background="{DynamicResource AppBackgroundBrush}">
|
||||
|
||||
<!-- ============ MENU (stile Electrum) ============ -->
|
||||
<!-- ============ MENU (Electrum style) ============ -->
|
||||
<Menu Grid.Row="0" IsVisible="{Binding IsDesktop}">
|
||||
<MenuItem Header="{Binding Loc[menu.file]}">
|
||||
<MenuItem Header="{Binding Loc[menu.file.new]}" Command="{Binding NewWalletCommand}"/>
|
||||
@@ -43,7 +43,7 @@
|
||||
Command="{Binding OpenHelpCommand}"/>
|
||||
</Menu>
|
||||
|
||||
<!-- Pulsanti Settings/Help mobile-only (il menu è nascosto su mobile) -->
|
||||
<!-- Settings/Help buttons — mobile only (the menu is hidden on mobile) -->
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Spacing="8"
|
||||
HorizontalAlignment="Right" Margin="8,4"
|
||||
IsVisible="{Binding IsMobile}">
|
||||
@@ -51,14 +51,14 @@
|
||||
<Button Content="{Binding Loc[menu.help]}" Command="{Binding OpenHelpCommand}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- ============ WIZARD DI SETUP (§15): un passo alla volta ============ -->
|
||||
<!-- ============ SETUP WIZARD (§15): one step at a time ============ -->
|
||||
<ScrollViewer Grid.Row="1" IsVisible="{Binding IsSetupVisible}">
|
||||
<StackPanel MaxWidth="560" Margin="24,40" Spacing="18"
|
||||
HorizontalAlignment="Center">
|
||||
<TextBlock Text="Palladium Wallet" FontSize="28" FontWeight="Bold"
|
||||
HorizontalAlignment="Center"/>
|
||||
|
||||
<!-- Passo 0 (primo avvio): dove salvare i dati -->
|
||||
<!-- Step 0 (first launch): where to store data -->
|
||||
<StackPanel IsVisible="{Binding IsStepDataLocation}" Spacing="12">
|
||||
<TextBlock Text="{Binding Loc[wiz.data.title]}" FontSize="18" FontWeight="Bold"/>
|
||||
<TextBlock Text="{Binding Loc[wiz.data.info]}" TextWrapping="Wrap" Foreground="{DynamicResource TextSecondaryBrush}"/>
|
||||
@@ -75,7 +75,7 @@
|
||||
Click="OnChooseDataFolderClick"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Passo 1: scelta iniziale -->
|
||||
<!-- Step 1: initial choice -->
|
||||
<StackPanel IsVisible="{Binding IsStepStart}" Spacing="12">
|
||||
<Button Content="{Binding Loc[wiz.open.btn]}" FontSize="16"
|
||||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
||||
@@ -95,7 +95,7 @@
|
||||
Command="{Binding WizardStartImportWifCommand}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Passo: scelta del wallet (più file presenti) -->
|
||||
<!-- Step: choose wallet (multiple files present) -->
|
||||
<StackPanel IsVisible="{Binding IsStepChooseWallet}" Spacing="12">
|
||||
<TextBlock Text="{Binding Loc[wiz.choose.title]}" FontSize="18" FontWeight="Bold"/>
|
||||
<ItemsControl ItemsSource="{Binding WalletList}">
|
||||
@@ -112,7 +112,7 @@
|
||||
<Button Content="{Binding Loc[wiz.back]}" Command="{Binding WizardBackCommand}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Passo: password del wallet esistente -->
|
||||
<!-- Step: password for existing wallet -->
|
||||
<StackPanel IsVisible="{Binding IsStepOpen}" Spacing="12">
|
||||
<TextBlock Text="{Binding Loc[wiz.open.title]}" FontSize="18" FontWeight="Bold"/>
|
||||
<TextBox PlaceholderText="{Binding Loc[wiz.open.placeholder]}"
|
||||
@@ -124,7 +124,7 @@
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Passo: mostra il nuovo seed -->
|
||||
<!-- Step: show the new seed -->
|
||||
<StackPanel IsVisible="{Binding IsStepShowSeed}" Spacing="12">
|
||||
<TextBlock Text="{Binding Loc[wiz.seed.title]}" FontSize="18" FontWeight="Bold"/>
|
||||
<Border BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="1"
|
||||
@@ -141,7 +141,7 @@
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Passo: conferma del seed -->
|
||||
<!-- Step: seed confirmation -->
|
||||
<StackPanel IsVisible="{Binding IsStepConfirmSeed}" Spacing="12">
|
||||
<TextBlock Text="{Binding Loc[wiz.confirm.title]}" FontSize="18" FontWeight="Bold"/>
|
||||
<TextBox PlaceholderText="{Binding Loc[wiz.confirm.placeholder]}"
|
||||
@@ -153,7 +153,7 @@
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Passo: inserimento seed (ripristino) -->
|
||||
<!-- Step: enter seed words (restore) -->
|
||||
<StackPanel IsVisible="{Binding IsStepWords}" Spacing="12">
|
||||
<TextBlock Text="{Binding Loc[wiz.words.title]}" FontSize="18" FontWeight="Bold"/>
|
||||
<TextBox PlaceholderText="{Binding Loc[wiz.words.placeholder]}"
|
||||
@@ -165,7 +165,7 @@
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Passo: passphrase opzionale -->
|
||||
<!-- Step: optional passphrase -->
|
||||
<StackPanel IsVisible="{Binding IsStepPassphrase}" Spacing="12">
|
||||
<TextBlock Text="{Binding Loc[wiz.passphrase.title]}" FontSize="18" FontWeight="Bold"/>
|
||||
<TextBox PlaceholderText="{Binding Loc[wiz.passphrase.placeholder]}"
|
||||
@@ -177,7 +177,7 @@
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Passo: import xpub/xprv estesa (SLIP-132) -->
|
||||
<!-- Step: import extended xpub/xprv (SLIP-132) -->
|
||||
<StackPanel IsVisible="{Binding IsStepImportXkey}" Spacing="12">
|
||||
<TextBlock Text="{Binding Loc[wiz.importxkey.title]}" FontSize="18" FontWeight="Bold"/>
|
||||
<TextBlock Text="{Binding Loc[wiz.importxkey.hint]}" TextWrapping="Wrap" Foreground="{DynamicResource TextSecondaryBrush}"/>
|
||||
@@ -193,7 +193,7 @@
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Passo: import chiave WIF singola -->
|
||||
<!-- Step: import single WIF key -->
|
||||
<StackPanel IsVisible="{Binding IsStepImportWif}" Spacing="12">
|
||||
<TextBlock Text="{Binding Loc[wiz.importwif.title]}" FontSize="18" FontWeight="Bold"/>
|
||||
<TextBlock Text="{Binding Loc[wiz.importwif.hint]}" TextWrapping="Wrap" Foreground="{DynamicResource TextSecondaryBrush}"/>
|
||||
@@ -206,7 +206,7 @@
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Passo: scelta del tipo di script/indirizzi -->
|
||||
<!-- Step: choose script/address type -->
|
||||
<StackPanel IsVisible="{Binding IsStepScriptType}" Spacing="12">
|
||||
<TextBlock Text="{Binding Loc[wiz.scripttype.title]}" FontSize="18" FontWeight="Bold"/>
|
||||
<TextBlock Text="{Binding Loc[wiz.scripttype.hint]}" TextWrapping="Wrap" Foreground="{DynamicResource TextSecondaryBrush}"/>
|
||||
@@ -254,11 +254,11 @@
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Passo finale: nome + password del file (cifratura, stile Electrum) -->
|
||||
<!-- Final step: wallet name + file password (encryption, Electrum style) -->
|
||||
<StackPanel IsVisible="{Binding IsStepPassword}" Spacing="16">
|
||||
<TextBlock Text="{Binding Loc[wiz.password.title]}" FontSize="18" FontWeight="Bold"/>
|
||||
|
||||
<!-- Sezione nome: bordo distinto dal gruppo password -->
|
||||
<!-- Name section: visually distinct from the password group -->
|
||||
<Border BorderBrush="{DynamicResource SystemAccentColor}"
|
||||
BorderThickness="0,0,0,0"
|
||||
Background="{DynamicResource SurfaceAltBrush}"
|
||||
@@ -271,10 +271,10 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Separatore visivo -->
|
||||
<!-- Visual separator -->
|
||||
<Separator Margin="0,0"/>
|
||||
|
||||
<!-- Sezione cifratura -->
|
||||
<!-- Encryption section -->
|
||||
<StackPanel Spacing="8">
|
||||
<TextBox PlaceholderText="{Binding Loc[wiz.password.placeholder]}"
|
||||
PasswordChar="●" Text="{Binding PasswordInput}"/>
|
||||
@@ -296,11 +296,11 @@
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- ============ PANNELLO WALLET ============ -->
|
||||
<!-- ============ WALLET PANEL ============ -->
|
||||
<Grid Grid.Row="1" RowDefinitions="Auto,*" IsVisible="{Binding IsWalletOpen}" Margin="16">
|
||||
|
||||
<!-- Testata: card del saldo. Connetti/sincronizza è automatico; chiudi
|
||||
wallet è in File. Lo stato connessione è nella barra in basso. -->
|
||||
<!-- Header: balance card. Connect/sync is automatic; close wallet is in File.
|
||||
Connection status is shown in the bottom bar. -->
|
||||
<Border Grid.Row="0" Classes="balance-hero" Margin="0,0,0,16">
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="{Binding Loc[addr.balance]}" Classes="label on-hero"/>
|
||||
@@ -313,7 +313,7 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Tab: Storico / Invia / Ricevi / Indirizzi / Contatti -->
|
||||
<!-- Tabs: History / Send / Receive / Addresses / Contacts -->
|
||||
<TabControl Grid.Row="1"
|
||||
SelectedIndex="{Binding SelectedTabIndex}"
|
||||
TabStripPlacement="Top">
|
||||
@@ -336,7 +336,7 @@
|
||||
</Style>
|
||||
</TabControl.Styles>
|
||||
|
||||
<!-- 1. Storico -->
|
||||
<!-- 1. History -->
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<StackPanel Spacing="2" HorizontalAlignment="Center">
|
||||
@@ -359,7 +359,7 @@
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:HistoryRow">
|
||||
<Panel Cursor="Hand">
|
||||
<!-- Desktop: 4 colonne fisse -->
|
||||
<!-- Desktop: 4 fixed columns -->
|
||||
<Grid ColumnDefinitions="90,160,*,70"
|
||||
IsVisible="{Binding $parent[UserControl].((vm:MainWindowViewModel)DataContext).IsDesktop}">
|
||||
<TextBlock Grid.Column="0" Text="{Binding Conferma}" Foreground="{DynamicResource TextSecondaryBrush}"/>
|
||||
@@ -369,7 +369,7 @@
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
<TextBlock Grid.Column="3" Text="{Binding Verificata}" Foreground="{DynamicResource SuccessBrush}"/>
|
||||
</Grid>
|
||||
<!-- Mobile: card verticale -->
|
||||
<!-- Mobile: vertical card -->
|
||||
<StackPanel Spacing="2"
|
||||
IsVisible="{Binding $parent[UserControl].((vm:MainWindowViewModel)DataContext).IsMobile}">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
@@ -389,7 +389,7 @@
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<!-- 2. Invia -->
|
||||
<!-- 2. Send -->
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<StackPanel Spacing="2" HorizontalAlignment="Center">
|
||||
@@ -404,7 +404,7 @@
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<StackPanel Spacing="10" Margin="8" MaxWidth="640" HorizontalAlignment="Left">
|
||||
<!-- Contatto rapido -->
|
||||
<!-- Quick contact -->
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Text="{Binding Loc[send.from.contact]}" VerticalAlignment="Center"/>
|
||||
<ComboBox ItemsSource="{Binding Contacts}"
|
||||
@@ -433,7 +433,7 @@
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</Grid>
|
||||
<!-- Desktop: riga amount + fee in 4 colonne -->
|
||||
<!-- Desktop: amount + fee row in 4 columns -->
|
||||
<Grid ColumnDefinitions="*,Auto,Auto,Auto" IsVisible="{Binding IsDesktop}">
|
||||
<TextBox Grid.Column="0" PlaceholderText="{Binding Loc[send.amount]}"
|
||||
Text="{Binding SendAmount}" IsEnabled="{Binding !SendAll}"/>
|
||||
@@ -446,7 +446,7 @@
|
||||
<TextBox Text="{Binding SendFeeRate}" MinWidth="60"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<!-- Mobile: amount e fee su righe separate -->
|
||||
<!-- Mobile: amount and fee on separate rows -->
|
||||
<StackPanel Spacing="6" IsVisible="{Binding IsMobile}">
|
||||
<Grid ColumnDefinitions="*,Auto,Auto">
|
||||
<TextBox Grid.Column="0" PlaceholderText="{Binding Loc[send.amount]}"
|
||||
@@ -472,7 +472,7 @@
|
||||
</StackPanel>
|
||||
</TabItem>
|
||||
|
||||
<!-- 3. Ricevi -->
|
||||
<!-- 3. Receive -->
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<StackPanel Spacing="2" HorizontalAlignment="Center">
|
||||
@@ -488,7 +488,7 @@
|
||||
</TabItem.Header>
|
||||
<StackPanel Spacing="10" Margin="8">
|
||||
<TextBlock Text="{Binding Loc[receive.next]}"/>
|
||||
<!-- Desktop: indirizzo + copia su una riga -->
|
||||
<!-- Desktop: address + copy on one row -->
|
||||
<StackPanel Orientation="Horizontal" Spacing="8" IsVisible="{Binding IsDesktop}">
|
||||
<SelectableTextBlock Text="{Binding ReceiveAddress}"
|
||||
FontFamily="monospace" FontSize="16"
|
||||
@@ -497,7 +497,7 @@
|
||||
Click="OnCopyReceiveAddressClick"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<!-- Mobile: indirizzo su riga intera + copia sotto -->
|
||||
<!-- Mobile: address on full row + copy below -->
|
||||
<StackPanel Spacing="6" IsVisible="{Binding IsMobile}">
|
||||
<SelectableTextBlock Text="{Binding ReceiveAddress}"
|
||||
FontFamily="monospace" FontSize="13"
|
||||
@@ -518,7 +518,7 @@
|
||||
</StackPanel>
|
||||
</TabItem>
|
||||
|
||||
<!-- 4. Indirizzi -->
|
||||
<!-- 4. Addresses -->
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<StackPanel Spacing="2" HorizontalAlignment="Center">
|
||||
@@ -533,7 +533,7 @@
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<Grid RowDefinitions="Auto,*" Margin="4">
|
||||
<!-- Intestazione colonne — solo desktop -->
|
||||
<!-- Column header — desktop only -->
|
||||
<Grid Grid.Row="0" ColumnDefinitions="90,60,*,140,60" Margin="12,4"
|
||||
IsVisible="{Binding IsDesktop}">
|
||||
<TextBlock Grid.Column="0" Text="{Binding Loc[addr.type]}" FontWeight="Bold"/>
|
||||
@@ -550,7 +550,7 @@
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:AddressRow">
|
||||
<Panel>
|
||||
<!-- Desktop: 5 colonne fisse -->
|
||||
<!-- Desktop: 5 fixed columns -->
|
||||
<Grid ColumnDefinitions="90,60,*,140,60"
|
||||
IsVisible="{Binding $parent[UserControl].((vm:MainWindowViewModel)DataContext).IsDesktop}">
|
||||
<TextBlock Grid.Column="0" Text="{Binding Tipo}" Foreground="{DynamicResource TextSecondaryBrush}"/>
|
||||
@@ -560,7 +560,7 @@
|
||||
<TextBlock Grid.Column="3" Text="{Binding Saldo}" FontFamily="monospace"/>
|
||||
<TextBlock Grid.Column="4" Text="{Binding NumTx}" Foreground="{DynamicResource TextSecondaryBrush}"/>
|
||||
</Grid>
|
||||
<!-- Mobile: card verticale -->
|
||||
<!-- Mobile: vertical card -->
|
||||
<StackPanel Spacing="1" Margin="0,3"
|
||||
IsVisible="{Binding $parent[UserControl].((vm:MainWindowViewModel)DataContext).IsMobile}">
|
||||
<TextBlock Text="{Binding Indirizzo}"
|
||||
@@ -580,7 +580,7 @@
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<!-- 5. Contatti -->
|
||||
<!-- 5. Contacts -->
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<StackPanel Spacing="2" HorizontalAlignment="Center">
|
||||
@@ -595,19 +595,19 @@
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<Grid RowDefinitions="Auto,*,Auto,Auto" Margin="4">
|
||||
<!-- Intestazioni colonne — solo desktop -->
|
||||
<!-- Column headers — desktop only -->
|
||||
<Grid Grid.Row="0" ColumnDefinitions="180,*" Margin="12,4"
|
||||
IsVisible="{Binding IsDesktop}">
|
||||
<TextBlock Grid.Column="0" Text="{Binding Loc[contacts.name]}" FontWeight="Bold"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding Loc[contacts.address]}" FontWeight="Bold"/>
|
||||
</Grid>
|
||||
<!-- Lista contatti -->
|
||||
<!-- Contacts list -->
|
||||
<ListBox Grid.Row="1" ItemsSource="{Binding Contacts}"
|
||||
SelectedItem="{Binding SelectedContactInList}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:ContactEntry">
|
||||
<Panel>
|
||||
<!-- Desktop: 2 colonne -->
|
||||
<!-- Desktop: 2 columns -->
|
||||
<Grid ColumnDefinitions="180,*"
|
||||
IsVisible="{Binding $parent[UserControl].((vm:MainWindowViewModel)DataContext).IsDesktop}">
|
||||
<TextBlock Grid.Column="0" Text="{Binding Name}"
|
||||
@@ -616,7 +616,7 @@
|
||||
FontFamily="monospace" FontSize="12"
|
||||
VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
<!-- Mobile: card verticale -->
|
||||
<!-- Mobile: vertical card -->
|
||||
<StackPanel Spacing="1" Margin="0,3"
|
||||
IsVisible="{Binding $parent[UserControl].((vm:MainWindowViewModel)DataContext).IsMobile}">
|
||||
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" FontSize="13"
|
||||
@@ -629,12 +629,12 @@
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<!-- Pulsante rimuovi selezionato -->
|
||||
<!-- Remove selected button -->
|
||||
<Button Grid.Row="2" Content="{Binding Loc[contacts.remove]}"
|
||||
Command="{Binding RemoveSelectedContactCommand}"
|
||||
IsEnabled="{Binding SelectedContactInList, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
Margin="0,6,0,0"/>
|
||||
<!-- Form aggiungi contatto — Desktop: 3 colonne -->
|
||||
<!-- Add contact form — Desktop: 3 columns -->
|
||||
<Grid Grid.Row="3" ColumnDefinitions="180,*,Auto" Margin="0,8,0,0"
|
||||
IsVisible="{Binding IsDesktop}">
|
||||
<TextBox Grid.Column="0" PlaceholderText="{Binding Loc[contacts.name.ph]}"
|
||||
@@ -645,7 +645,7 @@
|
||||
<Button Grid.Column="2" Content="{Binding Loc[contacts.add]}"
|
||||
Command="{Binding AddContactCommand}"/>
|
||||
</Grid>
|
||||
<!-- Form aggiungi contatto — Mobile: verticale -->
|
||||
<!-- Add contact form — Mobile: vertical -->
|
||||
<StackPanel Grid.Row="3" Spacing="6" Margin="0,8,0,0"
|
||||
IsVisible="{Binding IsMobile}">
|
||||
<TextBox PlaceholderText="{Binding Loc[contacts.name.ph]}"
|
||||
@@ -664,8 +664,8 @@
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- Barra di stato: messaggio a sinistra, stato connessione a destra.
|
||||
Lo stato connessione è cliccabile e apre le impostazioni del server. -->
|
||||
<!-- Status bar: message on the left, connection status on the right.
|
||||
The connection status is tappable and opens the server settings. -->
|
||||
<Border Grid.Row="2" Background="{DynamicResource SurfaceBrush}"
|
||||
BorderBrush="{DynamicResource BorderSubtleBrush}" BorderThickness="0,1,0,0"
|
||||
Padding="12,7">
|
||||
@@ -688,9 +688,9 @@
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- ============ OVERLAY DETTAGLIO INDIRIZZO ============ -->
|
||||
<!-- Overlay in-app invece di una Window separata: apertura/chiusura
|
||||
istantanee (niente create/destroy di una top-level window). -->
|
||||
<!-- ============ ADDRESS DETAIL OVERLAY ============ -->
|
||||
<!-- In-app overlay instead of a separate Window: instant open/close
|
||||
(no top-level window create/destroy). -->
|
||||
<Border Grid.Row="0" Grid.RowSpan="3"
|
||||
Background="{DynamicResource ScrimBrush}"
|
||||
Tapped="OnAddressOverlayBackdropTapped"
|
||||
@@ -705,7 +705,7 @@
|
||||
<TextBlock Text="{Binding Loc[addr.info.title]}"
|
||||
FontSize="18" FontWeight="Bold"/>
|
||||
|
||||
<!-- Indirizzo -->
|
||||
<!-- Address -->
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="{Binding Loc[addr.address]}" FontSize="11" Foreground="{DynamicResource TextSecondaryBrush}"/>
|
||||
<SelectableTextBlock Text="{Binding Address}"
|
||||
@@ -720,7 +720,7 @@
|
||||
FontFamily="monospace" FontSize="13"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Chiave pubblica -->
|
||||
<!-- Public key -->
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="{Binding Loc[addr.pubkey]}" FontSize="11" Foreground="{DynamicResource TextSecondaryBrush}"/>
|
||||
<SelectableTextBlock Text="{Binding PubKey}"
|
||||
@@ -728,7 +728,7 @@
|
||||
TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Chiave privata (solo se disponibile) -->
|
||||
<!-- Private key (only when available) -->
|
||||
<StackPanel Spacing="4" IsVisible="{Binding HasPrivKey}">
|
||||
<TextBlock Text="{Binding Loc[addr.privkey]}" FontSize="11" Foreground="{DynamicResource DangerBrush}"/>
|
||||
<SelectableTextBlock Text="{Binding PrivKey}"
|
||||
@@ -745,9 +745,9 @@
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<!-- ============ OVERLAY DETTAGLIO TRANSAZIONE ============ -->
|
||||
<!-- Overlay in-app (come indirizzo): appare subito con lo spinner, i dati
|
||||
arrivano dal server in background; chiusura istantanea. -->
|
||||
<!-- ============ TRANSACTION DETAIL OVERLAY ============ -->
|
||||
<!-- In-app overlay (like the address overlay): appears immediately with a
|
||||
spinner; data arrives from the server in the background; instant close. -->
|
||||
<Border Grid.Row="0" Grid.RowSpan="3"
|
||||
Background="{DynamicResource ScrimBrush}"
|
||||
Tapped="OnTxDetailsOverlayBackdropTapped"
|
||||
@@ -758,7 +758,7 @@
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<Panel Margin="24">
|
||||
|
||||
<!-- Caricamento -->
|
||||
<!-- Loading -->
|
||||
<StackPanel IsVisible="{Binding IsTxDetailsLoading}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="14">
|
||||
<ProgressBar IsIndeterminate="True" Width="220"/>
|
||||
@@ -766,8 +766,8 @@
|
||||
HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Contenuto (IsVisible sul contenitore esterno, così resta
|
||||
legato al VM principale; il DataContext interno è TxDetails) -->
|
||||
<!-- Content (IsVisible on the outer container so it stays bound
|
||||
to the main VM; the inner DataContext is TxDetails) -->
|
||||
<Grid IsVisible="{Binding !IsTxDetailsLoading}">
|
||||
<DockPanel DataContext="{Binding TxDetails}">
|
||||
<TextBlock DockPanel.Dock="Top" Text="{Binding Loc[tx.title]}"
|
||||
@@ -780,14 +780,14 @@
|
||||
<ScrollViewer>
|
||||
<StackPanel Spacing="18">
|
||||
|
||||
<!-- Header: importo netto in evidenza + badge stato/SPV -->
|
||||
<!-- Header: net amount highlighted + status/SPV badges -->
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock Text="{Binding AmountHeader}" Classes="label"/>
|
||||
<SelectableTextBlock Text="{Binding NetText}"
|
||||
FontFamily="monospace" FontFeatures="+tnum"
|
||||
FontSize="28" FontWeight="Bold"/>
|
||||
<WrapPanel>
|
||||
<!-- Badge coinbase: monete generate dal mining (nessun mittente) -->
|
||||
<!-- Coinbase badge: coins generated by mining (no sender) -->
|
||||
<Border Classes="chip" Margin="0,0,8,6" IsVisible="{Binding IsCoinbase}"
|
||||
Background="{DynamicResource WarningBrush}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
@@ -823,7 +823,7 @@
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Sezione: Panoramica -->
|
||||
<!-- Section: Overview -->
|
||||
<TextBlock Classes="section" Text="{Binding Loc[tx.sect.overview]}"/>
|
||||
<Border Classes="card" Padding="16,12">
|
||||
<StackPanel Spacing="10">
|
||||
@@ -839,7 +839,7 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Sezione: Importi e commissioni -->
|
||||
<!-- Section: Amounts and fees -->
|
||||
<TextBlock Classes="section" Text="{Binding Loc[tx.sect.amounts]}"/>
|
||||
<Border Classes="card" Padding="16,12">
|
||||
<StackPanel Spacing="10">
|
||||
@@ -864,7 +864,7 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Sezione: Dettagli tecnici -->
|
||||
<!-- Section: Technical details -->
|
||||
<TextBlock Classes="section" Text="{Binding Loc[tx.sect.tech]}"/>
|
||||
<Border Classes="card" Padding="16,12">
|
||||
<StackPanel Spacing="10">
|
||||
@@ -935,9 +935,9 @@
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<!-- ============ OVERLAY IMPOSTAZIONI SERVER ============ -->
|
||||
<!-- Stesso pattern dell'overlay indirizzo: apertura/chiusura istantanee.
|
||||
Accessibile da Impostazioni → Server. -->
|
||||
<!-- ============ SERVER SETTINGS OVERLAY ============ -->
|
||||
<!-- Same pattern as the address overlay: instant open/close.
|
||||
Accessible from Settings → Server. -->
|
||||
<Border Grid.Row="0" Grid.RowSpan="3"
|
||||
Background="{DynamicResource ScrimBrush}"
|
||||
Tapped="OnServerOverlayBackdropTapped"
|
||||
@@ -950,7 +950,7 @@
|
||||
<TextBlock Text="{Binding Loc[server.title]}"
|
||||
FontSize="18" FontWeight="Bold"/>
|
||||
|
||||
<!-- Host + porta — Desktop: 3 colonne -->
|
||||
<!-- Host + port — Desktop: 3 columns -->
|
||||
<Grid ColumnDefinitions="*,Auto,Auto" RowDefinitions="Auto,Auto"
|
||||
IsVisible="{Binding IsDesktop}">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Loc[server.host]}"
|
||||
@@ -966,7 +966,7 @@
|
||||
<CheckBox Grid.Row="1" Grid.Column="2" IsChecked="{Binding UseSsl}"
|
||||
Margin="10,2,0,0" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
<!-- Host + porta — Mobile: verticale, font più grandi -->
|
||||
<!-- Host + port — Mobile: vertical, larger font -->
|
||||
<StackPanel Spacing="10" IsVisible="{Binding IsMobile}">
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="{Binding Loc[server.host]}" FontSize="13" Foreground="{DynamicResource TextSecondaryBrush}"/>
|
||||
@@ -987,7 +987,7 @@
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Azioni — Desktop: in riga -->
|
||||
<!-- Actions — Desktop: in a row -->
|
||||
<StackPanel Orientation="Horizontal" Spacing="8" IsVisible="{Binding IsDesktop}">
|
||||
<Button Content="{Binding Loc[wallet.connect]}" Classes="accent"
|
||||
Command="{Binding ConnectAndSyncCommand}" IsEnabled="{Binding !IsSyncing}"/>
|
||||
@@ -996,7 +996,7 @@
|
||||
<Button Content="{Binding Loc[wallet.resetcert]}"
|
||||
Command="{Binding ResetCertificatesCommand}"/>
|
||||
</StackPanel>
|
||||
<!-- Azioni — Mobile: in colonna -->
|
||||
<!-- Actions — Mobile: stacked -->
|
||||
<StackPanel Spacing="6" IsVisible="{Binding IsMobile}">
|
||||
<Button Content="{Binding Loc[wallet.connect]}" Classes="accent"
|
||||
Command="{Binding ConnectAndSyncCommand}" IsEnabled="{Binding !IsSyncing}"
|
||||
@@ -1009,7 +1009,7 @@
|
||||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Stato connessione -->
|
||||
<!-- Connection status -->
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Ellipse Width="10" Height="10" Fill="{DynamicResource SuccessBrush}" VerticalAlignment="Center"
|
||||
IsVisible="{Binding IsConnected}"/>
|
||||
@@ -1019,7 +1019,7 @@
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Server conosciuti: clicca per riempire host/porta -->
|
||||
<!-- Known servers: click to populate host/port -->
|
||||
<TextBlock Text="{Binding Loc[server.known]}" FontSize="11" Foreground="{DynamicResource TextSecondaryBrush}"/>
|
||||
<ListBox ItemsSource="{Binding KnownServers}"
|
||||
SelectedItem="{Binding SelectedKnownServer}"
|
||||
@@ -1038,7 +1038,7 @@
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="net:KnownServer">
|
||||
<Panel>
|
||||
<!-- Desktop: host + porte in 2 colonne -->
|
||||
<!-- Desktop: host + ports in 2 columns -->
|
||||
<Grid ColumnDefinitions="*,Auto"
|
||||
IsVisible="{Binding $parent[UserControl].((vm:MainWindowViewModel)DataContext).IsDesktop}">
|
||||
<TextBlock Grid.Column="0" Text="{Binding Host}"
|
||||
@@ -1050,7 +1050,7 @@
|
||||
<Run Text=" ssl "/><Run Text="{Binding SslPort}"/>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<!-- Mobile: host sopra, porte sotto -->
|
||||
<!-- Mobile: host above, ports below -->
|
||||
<StackPanel Spacing="2" Margin="0,4"
|
||||
IsVisible="{Binding $parent[UserControl].((vm:MainWindowViewModel)DataContext).IsMobile}">
|
||||
<TextBlock Text="{Binding Host}"
|
||||
@@ -1078,8 +1078,8 @@
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<!-- ============ OVERLAY IMPOSTAZIONI ============ -->
|
||||
<!-- In-app invece dei sottomenu annidati: niente popup OS lenti su WSLg. -->
|
||||
<!-- ============ SETTINGS OVERLAY ============ -->
|
||||
<!-- In-app instead of nested submenus: avoids slow OS popups on WSLg. -->
|
||||
<Border Grid.Row="0" Grid.RowSpan="3"
|
||||
Background="{DynamicResource ScrimBrush}"
|
||||
Tapped="OnSettingsOverlayBackdropTapped"
|
||||
@@ -1093,7 +1093,7 @@
|
||||
<TextBlock Text="{Binding Loc[settings.title]}"
|
||||
FontSize="18" FontWeight="Bold"/>
|
||||
|
||||
<!-- Lingua -->
|
||||
<!-- Language -->
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock Text="{Binding Loc[settings.language]}" FontSize="11" Foreground="{DynamicResource TextSecondaryBrush}"/>
|
||||
<WrapPanel>
|
||||
@@ -1118,7 +1118,7 @@
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Unità -->
|
||||
<!-- Unit -->
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock Text="{Binding Loc[settings.unit]}" FontSize="11" Foreground="{DynamicResource TextSecondaryBrush}"/>
|
||||
<WrapPanel>
|
||||
@@ -1137,7 +1137,7 @@
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Server di indicizzazione (configurabile anche prima di aprire un wallet) -->
|
||||
<!-- Indexing server (configurable even before a wallet is open) -->
|
||||
<Button Content="{Binding Loc[settings.server]}"
|
||||
HorizontalAlignment="Left"
|
||||
Command="{Binding OpenServerSettingsCommand}"/>
|
||||
@@ -1150,8 +1150,8 @@
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<!-- ============ OVERLAY HELP / INFORMAZIONI ============ -->
|
||||
<!-- Stesso pattern dell'overlay impostazioni: apertura/chiusura istantanee. -->
|
||||
<!-- ============ HELP / ABOUT OVERLAY ============ -->
|
||||
<!-- Same pattern as the settings overlay: instant open/close. -->
|
||||
<Border Grid.Row="0" Grid.RowSpan="3"
|
||||
Background="{DynamicResource ScrimBrush}"
|
||||
Tapped="OnHelpOverlayBackdropTapped"
|
||||
|
||||
@@ -11,10 +11,10 @@ using PalladiumWallet.App.ViewModels;
|
||||
namespace PalladiumWallet.App.Views;
|
||||
|
||||
/// <summary>
|
||||
/// Vista radice dell'app, condivisa tra desktop (ospitata in <see cref="MainWindow"/>)
|
||||
/// e mobile (root single-view). Tutti gli overlay sono in-app, quindi non servono
|
||||
/// finestre separate. Le API legate al top-level (file picker, clipboard) si
|
||||
/// raggiungono via <see cref="TopLevel.GetTopLevel"/> perché un UserControl non le espone.
|
||||
/// App root view, shared between desktop (hosted in <see cref="MainWindow"/>)
|
||||
/// and mobile (single-view root). All overlays are in-app so no separate windows
|
||||
/// are needed. Top-level APIs (file picker, clipboard) are reached via
|
||||
/// <see cref="TopLevel.GetTopLevel"/> because a UserControl does not expose them.
|
||||
/// </summary>
|
||||
public partial class MainView : UserControl
|
||||
{
|
||||
@@ -47,8 +47,8 @@ public partial class MainView : UserControl
|
||||
if (sender is not ListBox lb || DataContext is not MainWindowViewModel vm) return;
|
||||
if (lb.SelectedItem is not HistoryRow row) return;
|
||||
|
||||
// Overlay in-app: appare subito con lo spinner, i dati arrivano dal
|
||||
// server in background. Niente top-level window (lenta da aprire/chiudere).
|
||||
// In-app overlay: appears immediately with a spinner; data arrives from
|
||||
// the server in the background. No top-level window (slow to open/close).
|
||||
_ = vm.ShowTransactionDetailsAsync(row.Txid);
|
||||
}
|
||||
|
||||
@@ -78,8 +78,8 @@ public partial class MainView : UserControl
|
||||
vm.ShowAddressInfo(row);
|
||||
}
|
||||
|
||||
// Chiusura dell'overlay dettaglio indirizzo: click sullo sfondo scuro
|
||||
// (solo sullo sfondo, non sulla scheda) o tasto Esc.
|
||||
// Close the address-detail overlay: click on the dark backdrop
|
||||
// (backdrop only, not the card itself) or press Esc.
|
||||
private void OnAddressOverlayBackdropTapped(object? sender, TappedEventArgs e)
|
||||
{
|
||||
if (!ReferenceEquals(e.Source, sender)) return;
|
||||
@@ -140,7 +140,7 @@ public partial class MainView : UserControl
|
||||
vm.IsHelpOpen = false;
|
||||
}
|
||||
|
||||
// Esc (desktop) o tasto Back (Android) chiudono l'overlay in primo piano.
|
||||
// Esc (desktop) or Back (Android) closes the topmost open overlay.
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
if ((e.Key == Key.Escape || e.Key == Key.Back) && DataContext is MainWindowViewModel vm)
|
||||
|
||||
Reference in New Issue
Block a user