feat(app): navbar restructure, contacts tab and address info window
- Reorder tabs: Storico / Invia / Ricevi / Indirizzi / Contatti - Invia: ComboBox to quick-fill recipient from saved contacts - Contatti: new tab to add/remove contacts (name + address), persisted to the wallet file via WalletDocument.Contacts - Indirizzi: clicking any address (left or right click) opens a modal window with derivation path, public key and private key as selectable text (no copy button needed — user selects and copies manually); AddressRow now carries PubKey, PrivKey, DerivPath pre-computed - AddressInfoWindow: new Window with SelectableTextBlock per field, private key section hidden for watch-only wallets - Loc: adds tab.contacts, addr.*, contacts.*, send.from.contact keys in all 6 languages
This commit is contained in:
@@ -93,6 +93,7 @@ public sealed class Loc
|
|||||||
["tab.history"] = ["Storico", "History", "Historial", "Historique", "Histórico", "Verlauf"],
|
["tab.history"] = ["Storico", "History", "Historial", "Historique", "Histórico", "Verlauf"],
|
||||||
["tab.addresses"] = ["Indirizzi", "Addresses", "Direcciones", "Adresses", "Endereços", "Adressen"],
|
["tab.addresses"] = ["Indirizzi", "Addresses", "Direcciones", "Adresses", "Endereços", "Adressen"],
|
||||||
["tab.send"] = ["Invia", "Send", "Enviar", "Envoyer", "Enviar", "Senden"],
|
["tab.send"] = ["Invia", "Send", "Enviar", "Envoyer", "Enviar", "Senden"],
|
||||||
|
["tab.contacts"] = ["Contatti", "Contacts", "Contactos", "Contacts", "Contatos", "Kontakte"],
|
||||||
["receive.next"] = ["Prossimo indirizzo non usato:", "Next unused address:", "Próxima dirección no usada:", "Prochaine adresse non utilisée :", "Próximo endereço não usado:", "Nächste ungenutzte Adresse:"],
|
["receive.next"] = ["Prossimo indirizzo non usato:", "Next unused address:", "Próxima dirección no usada:", "Prochaine adresse non utilisée :", "Próximo endereço não usado:", "Nächste ungenutzte Adresse:"],
|
||||||
["receive.hint"] = [
|
["receive.hint"] = [
|
||||||
"Ogni pagamento ricevuto qui comparirà nello storico alla prossima sincronizzazione.",
|
"Ogni pagamento ricevuto qui comparirà nello storico alla prossima sincronizzazione.",
|
||||||
@@ -105,8 +106,18 @@ public sealed class Loc
|
|||||||
["addr.index"] = ["Indice", "Index", "Índice", "Index", "Índice", "Index"],
|
["addr.index"] = ["Indice", "Index", "Índice", "Index", "Índice", "Index"],
|
||||||
["addr.address"] = ["Indirizzo", "Address", "Dirección", "Adresse", "Endereço", "Adresse"],
|
["addr.address"] = ["Indirizzo", "Address", "Dirección", "Adresse", "Endereço", "Adresse"],
|
||||||
["addr.balance"] = ["Saldo", "Balance", "Saldo", "Solde", "Saldo", "Saldo"],
|
["addr.balance"] = ["Saldo", "Balance", "Saldo", "Solde", "Saldo", "Saldo"],
|
||||||
|
["addr.copied"] = ["Indirizzo copiato negli appunti", "Address copied to clipboard", "Dirección copiada al portapapeles", "Adresse copiée dans le presse-papiers", "Endereço copiado para a área de transferência", "Adresse in die Zwischenablage kopiert"],
|
||||||
|
["addr.derivpath"] = ["Percorso di derivazione:", "Derivation path:", "Ruta de derivación:", "Chemin de dérivation :", "Caminho de derivação:", "Ableitungspfad:"],
|
||||||
|
["addr.pubkey"] = ["Chiave pubblica:", "Public key:", "Clave pública:", "Clé publique :", "Chave pública:", "Öffentlicher Schlüssel:"],
|
||||||
|
["addr.privkey"] = ["Chiave privata (WIF):", "Private key (WIF):", "Clave privada (WIF):", "Clé privée (WIF) :", "Chave privada (WIF):", "Privater Schlüssel (WIF):"],
|
||||||
|
["addr.show.privkey"] = ["Mostra", "Show", "Mostrar", "Afficher", "Mostrar", "Anzeigen"],
|
||||||
|
["addr.hide.privkey"] = ["Nascondi", "Hide", "Ocultar", "Masquer", "Ocultar", "Ausblenden"],
|
||||||
["addr.receive"] = ["ricezione", "receive", "recepción", "réception", "recebimento", "Empfang"],
|
["addr.receive"] = ["ricezione", "receive", "recepción", "réception", "recebimento", "Empfang"],
|
||||||
["addr.change"] = ["change", "change", "cambio", "monnaie", "troco", "Wechselgeld"],
|
["addr.change"] = ["change", "change", "cambio", "monnaie", "troco", "Wechselgeld"],
|
||||||
|
["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"],
|
||||||
|
["send.from.contact"] = ["Da contatti:", "From contacts:", "De contactos:", "Depuis les contacts :", "De contatos:", "Aus Kontakten:"],
|
||||||
|
["send.contact.hint"] = ["seleziona per riempire l'indirizzo", "select to fill address", "selecciona para rellenar la dirección", "sélectionner pour remplir l'adresse", "selecione para preencher o endereço", "auswählen um Adresse einzufügen"],
|
||||||
["send.to"] = ["Indirizzo destinatario", "Recipient address", "Dirección destinataria", "Adresse du destinataire", "Endereço do destinatário", "Empfängeradresse"],
|
["send.to"] = ["Indirizzo destinatario", "Recipient address", "Dirección destinataria", "Adresse du destinataire", "Endereço do destinatário", "Empfängeradresse"],
|
||||||
["send.amount"] = ["Importo", "Amount", "Importe", "Montant", "Valor", "Betrag"],
|
["send.amount"] = ["Importo", "Amount", "Importe", "Montant", "Valor", "Betrag"],
|
||||||
["send.all"] = ["Invia tutto", "Send all", "Enviar todo", "Tout envoyer", "Enviar tudo", "Alles senden"],
|
["send.all"] = ["Invia tutto", "Send all", "Enviar todo", "Tout envoyer", "Enviar tudo", "Alles senden"],
|
||||||
@@ -217,6 +228,15 @@ public sealed class Loc
|
|||||||
"SSL-Zertifikate zurückgesetzt: Verbindung erneut versuchen."],
|
"SSL-Zertifikate zurückgesetzt: Verbindung erneut versuchen."],
|
||||||
["msg.error"] = ["Errore", "Error", "Error", "Erreur", "Erro", "Fehler"],
|
["msg.error"] = ["Errore", "Error", "Error", "Erreur", "Erro", "Fehler"],
|
||||||
|
|
||||||
|
// Contatti
|
||||||
|
["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"],
|
||||||
|
["contacts.address.ph"] = ["Indirizzo blockchain", "Blockchain address", "Dirección blockchain", "Adresse blockchain", "Endereço blockchain", "Blockchain-Adresse"],
|
||||||
|
["contacts.add"] = ["Aggiungi", "Add", "Agregar", "Ajouter", "Adicionar", "Hinzufügen"],
|
||||||
|
["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
|
// Finestra impostazioni
|
||||||
["settings.title"] = ["Impostazioni", "Settings", "Configuración", "Paramètres", "Configurações", "Einstellungen"],
|
["settings.title"] = ["Impostazioni", "Settings", "Configuración", "Paramètres", "Configurações", "Einstellungen"],
|
||||||
["settings.language"] = ["Lingua", "Language", "Idioma", "Langue", "Idioma", "Sprache"],
|
["settings.language"] = ["Lingua", "Language", "Idioma", "Langue", "Idioma", "Sprache"],
|
||||||
|
|||||||
@@ -21,8 +21,24 @@ namespace PalladiumWallet.App.ViewModels;
|
|||||||
/// <summary>Riga dello storico transazioni per la vista.</summary>
|
/// <summary>Riga dello storico transazioni per la vista.</summary>
|
||||||
public sealed record HistoryRow(string Conferma, string Importo, string Txid, string Verificata);
|
public sealed record HistoryRow(string Conferma, string Importo, string Txid, string Verificata);
|
||||||
|
|
||||||
/// <summary>Riga della vista indirizzi (stile Electrum): saldo e uso per indirizzo.</summary>
|
/// <summary>Riga della vista indirizzi con chiavi e derivation path pre-calcolati.</summary>
|
||||||
public sealed record AddressRow(string Tipo, int Indice, string Indirizzo, string Saldo, string NumTx);
|
public sealed record AddressRow(
|
||||||
|
string Tipo, int Indice, string Indirizzo, string Saldo, string NumTx,
|
||||||
|
bool IsChange = false, string PubKey = "", string PrivKey = "", string DerivPath = "")
|
||||||
|
{
|
||||||
|
public bool HasPrivKey => !string.IsNullOrEmpty(PrivKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Dati completi di un indirizzo passati alla finestra di dettaglio.</summary>
|
||||||
|
public sealed record AddressInfo(
|
||||||
|
Localization.Loc Loc,
|
||||||
|
string Address, string DerivPath, string PubKey, string PrivKey)
|
||||||
|
{
|
||||||
|
public bool HasPrivKey => !string.IsNullOrEmpty(PrivKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Contatto in rubrica: nome + indirizzo blockchain.</summary>
|
||||||
|
public sealed record ContactEntry(string Name, string Address);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ViewModel unico dell'applicazione (wizard §15 ridotto + dashboard):
|
/// ViewModel unico dell'applicazione (wizard §15 ridotto + dashboard):
|
||||||
@@ -108,6 +124,18 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
private string Fmt(long sats, bool withLabel = true) =>
|
private string Fmt(long sats, bool withLabel = true) =>
|
||||||
CoinAmount.FormatIn(sats, _config.Unit, withLabel);
|
CoinAmount.FormatIn(sats, _config.Unit, withLabel);
|
||||||
|
|
||||||
|
/// <summary>Chiave privata WIF per un indirizzo; stringa vuota se watch-only.</summary>
|
||||||
|
private string KeyWif(bool isChange, int index)
|
||||||
|
{
|
||||||
|
if (_account is null or { IsWatchOnly: true }) return "";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _account.GetExtPrivateKey(isChange, index)
|
||||||
|
.PrivateKey.GetWif(PalladiumNetworks.For(Net)).ToString();
|
||||||
|
}
|
||||||
|
catch { return ""; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>File in attesa di password (apertura da menu File → Apri).</summary>
|
/// <summary>File in attesa di password (apertura da menu File → Apri).</summary>
|
||||||
private string? _pendingOpenPath;
|
private string? _pendingOpenPath;
|
||||||
|
|
||||||
@@ -216,6 +244,73 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
|
|
||||||
public ObservableCollection<AddressRow> Addresses { get; } = [];
|
public ObservableCollection<AddressRow> Addresses { get; } = [];
|
||||||
|
|
||||||
|
// ---- tab indirizzi ----
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private AddressRow? selectedAddressRow;
|
||||||
|
|
||||||
|
// ---- rubrica contatti ----
|
||||||
|
|
||||||
|
public ObservableCollection<ContactEntry> Contacts { get; } = [];
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private ContactEntry? selectedContactInList;
|
||||||
|
|
||||||
|
/// <summary>Contatto selezionato nella ComboBox del pannello Invia: riempie SendTo.</summary>
|
||||||
|
[ObservableProperty]
|
||||||
|
private ContactEntry? sendToContact;
|
||||||
|
|
||||||
|
partial void OnSendToContactChanged(ContactEntry? value)
|
||||||
|
{
|
||||||
|
if (value is not null)
|
||||||
|
SendTo = value.Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string newContactName = "";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string newContactAddress = "";
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void AddContact()
|
||||||
|
{
|
||||||
|
var name = NewContactName.Trim();
|
||||||
|
var addr = NewContactAddress.Trim();
|
||||||
|
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(addr)) return;
|
||||||
|
Contacts.Add(new ContactEntry(name, addr));
|
||||||
|
NewContactName = NewContactAddress = "";
|
||||||
|
PersistContacts();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void RemoveSelectedContact()
|
||||||
|
{
|
||||||
|
if (SelectedContactInList is { } c)
|
||||||
|
{
|
||||||
|
Contacts.Remove(c);
|
||||||
|
SelectedContactInList = null;
|
||||||
|
PersistContacts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PersistContacts()
|
||||||
|
{
|
||||||
|
if (_doc is null || _walletPath is null) return;
|
||||||
|
_doc.Contacts = Contacts
|
||||||
|
.Select(c => new PalladiumWallet.Core.Storage.StoredContact { Name = c.Name, Address = c.Address })
|
||||||
|
.ToList();
|
||||||
|
WalletStore.Save(_doc, _walletPath, _password);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadContacts()
|
||||||
|
{
|
||||||
|
Contacts.Clear();
|
||||||
|
if (_doc is null) return;
|
||||||
|
foreach (var c in _doc.Contacts)
|
||||||
|
Contacts.Add(new ContactEntry(c.Name, c.Address));
|
||||||
|
}
|
||||||
|
|
||||||
// ---- pannello invia ----
|
// ---- pannello invia ----
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
@@ -495,6 +590,7 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
|
|
||||||
NetworkInfo = $"{doc.Network} · {doc.ScriptKind} · m/{doc.AccountPath}"
|
NetworkInfo = $"{doc.Network} · {doc.ScriptKind} · m/{doc.AccountPath}"
|
||||||
+ (doc.IsWatchOnly ? " · watch-only" : "");
|
+ (doc.IsWatchOnly ? " · watch-only" : "");
|
||||||
|
LoadContacts();
|
||||||
ApplyCache(doc.Cache);
|
ApplyCache(doc.Cache);
|
||||||
IsWalletOpen = true;
|
IsWalletOpen = true;
|
||||||
StatusMessage = Loc.Tr("msg.opened");
|
StatusMessage = Loc.Tr("msg.opened");
|
||||||
@@ -516,8 +612,12 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
// Prima della sincronizzazione si mostrano i primi indirizzi derivati.
|
// Prima della sincronizzazione si mostrano i primi indirizzi derivati.
|
||||||
Addresses.Clear();
|
Addresses.Clear();
|
||||||
for (var i = 0; i < 10; i++)
|
for (var i = 0; i < 10; i++)
|
||||||
Addresses.Add(new AddressRow("ricezione", i,
|
Addresses.Add(new AddressRow(_loc["addr.receive"], i,
|
||||||
_account.GetReceiveAddress(i).ToString(), "—", "—"));
|
_account.GetReceiveAddress(i).ToString(), "—", "—",
|
||||||
|
false,
|
||||||
|
_account.GetPublicKey(false, i).ToHex(),
|
||||||
|
KeyWif(false, i),
|
||||||
|
$"m/{_doc!.AccountPath}/0/{i}"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
BalanceText = Fmt(cache.ConfirmedSats);
|
BalanceText = Fmt(cache.ConfirmedSats);
|
||||||
@@ -540,11 +640,15 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
Addresses.Clear();
|
Addresses.Clear();
|
||||||
foreach (var a in cache.Addresses)
|
foreach (var a in cache.Addresses)
|
||||||
Addresses.Add(new AddressRow(
|
Addresses.Add(new AddressRow(
|
||||||
a.IsChange ? "change" : "ricezione",
|
a.IsChange ? _loc["addr.change"] : _loc["addr.receive"],
|
||||||
a.Index,
|
a.Index,
|
||||||
a.Address,
|
a.Address,
|
||||||
a.BalanceSats > 0 ? Fmt(a.BalanceSats, withLabel: false) : (a.TxCount > 0 ? "0" : "—"),
|
a.BalanceSats > 0 ? Fmt(a.BalanceSats, withLabel: false) : (a.TxCount > 0 ? "0" : "—"),
|
||||||
a.TxCount > 0 ? a.TxCount.ToString() : "—"));
|
a.TxCount > 0 ? a.TxCount.ToString() : "—",
|
||||||
|
a.IsChange,
|
||||||
|
_account.GetPublicKey(a.IsChange, a.Index).ToHex(),
|
||||||
|
KeyWif(a.IsChange, a.Index),
|
||||||
|
$"m/{_doc!.AccountPath}/{(a.IsChange ? 1 : 0)}/{a.Index}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- comandi wallet ----------
|
// ---------- comandi wallet ----------
|
||||||
@@ -757,6 +861,10 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
_pendingSend = null;
|
_pendingSend = null;
|
||||||
HasPendingSend = false;
|
HasPendingSend = false;
|
||||||
History.Clear();
|
History.Clear();
|
||||||
|
Contacts.Clear();
|
||||||
|
SelectedContactInList = null;
|
||||||
|
SendToContact = null;
|
||||||
|
SelectedAddressRow = null;
|
||||||
IsWalletOpen = false;
|
IsWalletOpen = false;
|
||||||
IsConnected = false;
|
IsConnected = false;
|
||||||
ConnectionStatus = Loc.Tr("conn.none");
|
ConnectionStatus = Loc.Tr("conn.none");
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="using:PalladiumWallet.App.ViewModels"
|
||||||
|
x:Class="PalladiumWallet.App.Views.AddressInfoWindow"
|
||||||
|
x:DataType="vm:AddressInfo"
|
||||||
|
Title="{Binding Loc[addr.info.title]}"
|
||||||
|
Width="560" SizeToContent="Height"
|
||||||
|
CanResize="False"
|
||||||
|
WindowStartupLocation="CenterOwner">
|
||||||
|
|
||||||
|
<StackPanel Margin="24" Spacing="16">
|
||||||
|
|
||||||
|
<!-- Indirizzo -->
|
||||||
|
<StackPanel Spacing="4">
|
||||||
|
<TextBlock Text="{Binding Loc[addr.address]}" FontSize="11" Foreground="Gray"/>
|
||||||
|
<SelectableTextBlock Text="{Binding Address}"
|
||||||
|
FontFamily="monospace" FontSize="13"
|
||||||
|
TextWrapping="Wrap"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Derivation path -->
|
||||||
|
<StackPanel Spacing="4">
|
||||||
|
<TextBlock Text="{Binding Loc[addr.derivpath]}" FontSize="11" Foreground="Gray"/>
|
||||||
|
<SelectableTextBlock Text="{Binding DerivPath}"
|
||||||
|
FontFamily="monospace" FontSize="13"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Chiave pubblica -->
|
||||||
|
<StackPanel Spacing="4">
|
||||||
|
<TextBlock Text="{Binding Loc[addr.pubkey]}" FontSize="11" Foreground="Gray"/>
|
||||||
|
<SelectableTextBlock Text="{Binding PubKey}"
|
||||||
|
FontFamily="monospace" FontSize="12"
|
||||||
|
TextWrapping="Wrap"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Chiave privata (solo se disponibile) -->
|
||||||
|
<StackPanel Spacing="4" IsVisible="{Binding HasPrivKey}">
|
||||||
|
<TextBlock Text="{Binding Loc[addr.privkey]}" FontSize="11" Foreground="OrangeRed"/>
|
||||||
|
<SelectableTextBlock Text="{Binding PrivKey}"
|
||||||
|
FontFamily="monospace" FontSize="12"
|
||||||
|
Foreground="OrangeRed"
|
||||||
|
TextWrapping="Wrap"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Button Content="{Binding Loc[addr.close]}"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Click="OnCloseClick"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using PalladiumWallet.App.ViewModels;
|
||||||
|
|
||||||
|
namespace PalladiumWallet.App.Views;
|
||||||
|
|
||||||
|
public partial class AddressInfoWindow : Window
|
||||||
|
{
|
||||||
|
public AddressInfoWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddressInfoWindow(AddressInfo info) : this()
|
||||||
|
{
|
||||||
|
DataContext = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCloseClick(object? sender, RoutedEventArgs e) => Close();
|
||||||
|
}
|
||||||
+101
-36
@@ -222,18 +222,10 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Tab: Ricevi / Storico / Indirizzi / Invia -->
|
<!-- Tab: Storico / Invia / Ricevi / Indirizzi / Contatti -->
|
||||||
<TabControl Grid.Row="2">
|
<TabControl Grid.Row="2">
|
||||||
<TabItem Header="{Binding Loc[tab.receive]}">
|
|
||||||
<StackPanel Spacing="10" Margin="8">
|
|
||||||
<TextBlock Text="{Binding Loc[receive.next]}"/>
|
|
||||||
<SelectableTextBlock Text="{Binding ReceiveAddress}"
|
|
||||||
FontFamily="monospace" FontSize="16"/>
|
|
||||||
<TextBlock Text="{Binding Loc[receive.hint]}"
|
|
||||||
Foreground="Gray" FontSize="12" TextWrapping="Wrap"/>
|
|
||||||
</StackPanel>
|
|
||||||
</TabItem>
|
|
||||||
|
|
||||||
|
<!-- 1. Storico -->
|
||||||
<TabItem Header="{Binding Loc[tab.history]}">
|
<TabItem Header="{Binding Loc[tab.history]}">
|
||||||
<ListBox ItemsSource="{Binding History}" Margin="4">
|
<ListBox ItemsSource="{Binding History}" Margin="4">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
@@ -250,34 +242,23 @@
|
|||||||
</ListBox>
|
</ListBox>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
<TabItem Header="{Binding Loc[tab.addresses]}">
|
<!-- 2. Invia -->
|
||||||
<Grid RowDefinitions="Auto,*" Margin="4">
|
|
||||||
<Grid Grid.Row="0" ColumnDefinitions="90,60,*,140,60" Margin="12,4">
|
|
||||||
<TextBlock Grid.Column="0" Text="{Binding Loc[addr.type]}" FontWeight="Bold"/>
|
|
||||||
<TextBlock Grid.Column="1" Text="{Binding Loc[addr.index]}" FontWeight="Bold"/>
|
|
||||||
<TextBlock Grid.Column="2" Text="{Binding Loc[addr.address]}" FontWeight="Bold"/>
|
|
||||||
<TextBlock Grid.Column="3" Text="{Binding Loc[addr.balance]}" FontWeight="Bold"/>
|
|
||||||
<TextBlock Grid.Column="4" Text="Tx" FontWeight="Bold"/>
|
|
||||||
</Grid>
|
|
||||||
<ListBox Grid.Row="1" ItemsSource="{Binding Addresses}">
|
|
||||||
<ListBox.ItemTemplate>
|
|
||||||
<DataTemplate x:DataType="vm:AddressRow">
|
|
||||||
<Grid ColumnDefinitions="90,60,*,140,60">
|
|
||||||
<TextBlock Grid.Column="0" Text="{Binding Tipo}" Foreground="Gray"/>
|
|
||||||
<TextBlock Grid.Column="1" Text="{Binding Indice}" Foreground="Gray"/>
|
|
||||||
<SelectableTextBlock Grid.Column="2" Text="{Binding Indirizzo}"
|
|
||||||
FontFamily="monospace" FontSize="13"/>
|
|
||||||
<TextBlock Grid.Column="3" Text="{Binding Saldo}" FontFamily="monospace"/>
|
|
||||||
<TextBlock Grid.Column="4" Text="{Binding NumTx}" Foreground="Gray"/>
|
|
||||||
</Grid>
|
|
||||||
</DataTemplate>
|
|
||||||
</ListBox.ItemTemplate>
|
|
||||||
</ListBox>
|
|
||||||
</Grid>
|
|
||||||
</TabItem>
|
|
||||||
|
|
||||||
<TabItem Header="{Binding Loc[tab.send]}">
|
<TabItem Header="{Binding Loc[tab.send]}">
|
||||||
<StackPanel Spacing="10" Margin="8" MaxWidth="640" HorizontalAlignment="Left">
|
<StackPanel Spacing="10" Margin="8" MaxWidth="640" HorizontalAlignment="Left">
|
||||||
|
<!-- Contatto rapido -->
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<TextBlock Text="{Binding Loc[send.from.contact]}" VerticalAlignment="Center"/>
|
||||||
|
<ComboBox ItemsSource="{Binding Contacts}"
|
||||||
|
SelectedItem="{Binding SendToContact}"
|
||||||
|
PlaceholderText="{Binding Loc[send.contact.hint]}"
|
||||||
|
MinWidth="220">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:ContactEntry">
|
||||||
|
<TextBlock Text="{Binding Name}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
<TextBox PlaceholderText="{Binding Loc[send.to]}" Text="{Binding SendTo}"
|
<TextBox PlaceholderText="{Binding Loc[send.to]}" Text="{Binding SendTo}"
|
||||||
FontFamily="monospace"/>
|
FontFamily="monospace"/>
|
||||||
<Grid ColumnDefinitions="*,Auto,Auto,Auto">
|
<Grid ColumnDefinitions="*,Auto,Auto,Auto">
|
||||||
@@ -302,6 +283,90 @@
|
|||||||
FontSize="13"/>
|
FontSize="13"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
|
<!-- 3. Ricevi -->
|
||||||
|
<TabItem Header="{Binding Loc[tab.receive]}">
|
||||||
|
<StackPanel Spacing="10" Margin="8">
|
||||||
|
<TextBlock Text="{Binding Loc[receive.next]}"/>
|
||||||
|
<SelectableTextBlock Text="{Binding ReceiveAddress}"
|
||||||
|
FontFamily="monospace" FontSize="16"/>
|
||||||
|
<TextBlock Text="{Binding Loc[receive.hint]}"
|
||||||
|
Foreground="Gray" FontSize="12" TextWrapping="Wrap"/>
|
||||||
|
</StackPanel>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<!-- 4. Indirizzi -->
|
||||||
|
<TabItem Header="{Binding Loc[tab.addresses]}">
|
||||||
|
<Grid RowDefinitions="Auto,*" Margin="4">
|
||||||
|
<Grid Grid.Row="0" ColumnDefinitions="90,60,*,140,60" Margin="12,4">
|
||||||
|
<TextBlock Grid.Column="0" Text="{Binding Loc[addr.type]}" FontWeight="Bold"/>
|
||||||
|
<TextBlock Grid.Column="1" Text="{Binding Loc[addr.index]}" FontWeight="Bold"/>
|
||||||
|
<TextBlock Grid.Column="2" Text="{Binding Loc[addr.address]}" FontWeight="Bold"/>
|
||||||
|
<TextBlock Grid.Column="3" Text="{Binding Loc[addr.balance]}" FontWeight="Bold"/>
|
||||||
|
<TextBlock Grid.Column="4" Text="Tx" FontWeight="Bold"/>
|
||||||
|
</Grid>
|
||||||
|
<ListBox Grid.Row="1" ItemsSource="{Binding Addresses}"
|
||||||
|
SelectedItem="{Binding SelectedAddressRow}"
|
||||||
|
x:Name="AddressesListBox"
|
||||||
|
Tapped="OnAddressListTapped"
|
||||||
|
PointerPressed="OnAddressListPointerPressed">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:AddressRow">
|
||||||
|
<Grid ColumnDefinitions="90,60,*,140,60">
|
||||||
|
<TextBlock Grid.Column="0" Text="{Binding Tipo}" Foreground="Gray"/>
|
||||||
|
<TextBlock Grid.Column="1" Text="{Binding Indice}" Foreground="Gray"/>
|
||||||
|
<TextBlock Grid.Column="2" Text="{Binding Indirizzo}"
|
||||||
|
FontFamily="monospace" FontSize="13"/>
|
||||||
|
<TextBlock Grid.Column="3" Text="{Binding Saldo}" FontFamily="monospace"/>
|
||||||
|
<TextBlock Grid.Column="4" Text="{Binding NumTx}" Foreground="Gray"/>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</Grid>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<!-- 5. Contatti -->
|
||||||
|
<TabItem Header="{Binding Loc[tab.contacts]}">
|
||||||
|
<Grid RowDefinitions="Auto,*,Auto,Auto" Margin="4">
|
||||||
|
<!-- Intestazioni colonne -->
|
||||||
|
<Grid Grid.Row="0" ColumnDefinitions="180,*" Margin="12,4">
|
||||||
|
<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 -->
|
||||||
|
<ListBox Grid.Row="1" ItemsSource="{Binding Contacts}"
|
||||||
|
SelectedItem="{Binding SelectedContactInList}">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:ContactEntry">
|
||||||
|
<Grid ColumnDefinitions="180,*">
|
||||||
|
<TextBlock Grid.Column="0" Text="{Binding Name}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<SelectableTextBlock Grid.Column="1" Text="{Binding Address}"
|
||||||
|
FontFamily="monospace" FontSize="12"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
<!-- Pulsante rimuovi selezionato -->
|
||||||
|
<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 -->
|
||||||
|
<Grid Grid.Row="3" ColumnDefinitions="180,*,Auto" Margin="0,8,0,0">
|
||||||
|
<TextBox Grid.Column="0" PlaceholderText="{Binding Loc[contacts.name.ph]}"
|
||||||
|
Text="{Binding NewContactName}" Margin="0,0,6,0"/>
|
||||||
|
<TextBox Grid.Column="1" PlaceholderText="{Binding Loc[contacts.address.ph]}"
|
||||||
|
Text="{Binding NewContactAddress}" FontFamily="monospace"
|
||||||
|
Margin="0,0,6,0"/>
|
||||||
|
<Button Grid.Column="2" Content="{Binding Loc[contacts.add]}"
|
||||||
|
Command="{Binding AddContactCommand}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
</TabControl>
|
</TabControl>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Input.Platform;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
using PalladiumWallet.App.ViewModels;
|
using PalladiumWallet.App.ViewModels;
|
||||||
|
|
||||||
namespace PalladiumWallet.App.Views;
|
namespace PalladiumWallet.App.Views;
|
||||||
@@ -13,7 +18,6 @@ public partial class MainWindow : Window
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>File → Apri wallet da file (il picker richiede il TopLevel, da qui).</summary>
|
|
||||||
private async void OnOpenWalletFileClick(object? sender, RoutedEventArgs e)
|
private async void OnOpenWalletFileClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (DataContext is not MainWindowViewModel vm)
|
if (DataContext is not MainWindowViewModel vm)
|
||||||
@@ -32,4 +36,26 @@ public partial class MainWindow : Window
|
|||||||
if (files.FirstOrDefault()?.TryGetLocalPath() is { } path)
|
if (files.FirstOrDefault()?.TryGetLocalPath() is { } path)
|
||||||
vm.OpenFromPath(path);
|
vm.OpenFromPath(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void OnAddressListTapped(object? sender, TappedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is not MainWindowViewModel vm || vm.SelectedAddressRow is not { } row)
|
||||||
|
return;
|
||||||
|
var info = new AddressInfo(vm.Loc, row.Indirizzo, row.DerivPath, row.PubKey, row.PrivKey);
|
||||||
|
await new AddressInfoWindow(info).ShowDialog(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnAddressListPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!e.GetCurrentPoint(null).Properties.IsRightButtonPressed) return;
|
||||||
|
if (sender is not ListBox lb || DataContext is not MainWindowViewModel vm) return;
|
||||||
|
|
||||||
|
var item = (e.Source as Visual)?.FindAncestorOfType<ListBoxItem>();
|
||||||
|
if (item is not { DataContext: AddressRow row }) return;
|
||||||
|
|
||||||
|
lb.SelectedItem = row;
|
||||||
|
|
||||||
|
var info = new AddressInfo(vm.Loc, row.Indirizzo, row.DerivPath, row.PubKey, row.PrivKey);
|
||||||
|
await new AddressInfoWindow(info).ShowDialog(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user