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.addresses"] = ["Indirizzi", "Addresses", "Direcciones", "Adresses", "Endereços", "Adressen"],
|
||||
["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.hint"] = [
|
||||
"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.address"] = ["Indirizzo", "Address", "Dirección", "Adresse", "Endereço", "Adresse"],
|
||||
["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.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.amount"] = ["Importo", "Amount", "Importe", "Montant", "Valor", "Betrag"],
|
||||
["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."],
|
||||
["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
|
||||
["settings.title"] = ["Impostazioni", "Settings", "Configuración", "Paramètres", "Configurações", "Einstellungen"],
|
||||
["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>
|
||||
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>
|
||||
public sealed record AddressRow(string Tipo, int Indice, string Indirizzo, string Saldo, string NumTx);
|
||||
/// <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,
|
||||
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>
|
||||
/// 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) =>
|
||||
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>
|
||||
private string? _pendingOpenPath;
|
||||
|
||||
@@ -216,6 +244,73 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
|
||||
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 ----
|
||||
|
||||
[ObservableProperty]
|
||||
@@ -495,6 +590,7 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
|
||||
NetworkInfo = $"{doc.Network} · {doc.ScriptKind} · m/{doc.AccountPath}"
|
||||
+ (doc.IsWatchOnly ? " · watch-only" : "");
|
||||
LoadContacts();
|
||||
ApplyCache(doc.Cache);
|
||||
IsWalletOpen = true;
|
||||
StatusMessage = Loc.Tr("msg.opened");
|
||||
@@ -516,8 +612,12 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
// Prima della sincronizzazione si mostrano i primi indirizzi derivati.
|
||||
Addresses.Clear();
|
||||
for (var i = 0; i < 10; i++)
|
||||
Addresses.Add(new AddressRow("ricezione", i,
|
||||
_account.GetReceiveAddress(i).ToString(), "—", "—"));
|
||||
Addresses.Add(new AddressRow(_loc["addr.receive"], i,
|
||||
_account.GetReceiveAddress(i).ToString(), "—", "—",
|
||||
false,
|
||||
_account.GetPublicKey(false, i).ToHex(),
|
||||
KeyWif(false, i),
|
||||
$"m/{_doc!.AccountPath}/0/{i}"));
|
||||
return;
|
||||
}
|
||||
BalanceText = Fmt(cache.ConfirmedSats);
|
||||
@@ -540,11 +640,15 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
Addresses.Clear();
|
||||
foreach (var a in cache.Addresses)
|
||||
Addresses.Add(new AddressRow(
|
||||
a.IsChange ? "change" : "ricezione",
|
||||
a.IsChange ? _loc["addr.change"] : _loc["addr.receive"],
|
||||
a.Index,
|
||||
a.Address,
|
||||
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 ----------
|
||||
@@ -757,6 +861,10 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
_pendingSend = null;
|
||||
HasPendingSend = false;
|
||||
History.Clear();
|
||||
Contacts.Clear();
|
||||
SelectedContactInList = null;
|
||||
SendToContact = null;
|
||||
SelectedAddressRow = null;
|
||||
IsWalletOpen = false;
|
||||
IsConnected = false;
|
||||
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>
|
||||
</Border>
|
||||
|
||||
<!-- Tab: Ricevi / Storico / Indirizzi / Invia -->
|
||||
<!-- Tab: Storico / Invia / Ricevi / Indirizzi / Contatti -->
|
||||
<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]}">
|
||||
<ListBox ItemsSource="{Binding History}" Margin="4">
|
||||
<ListBox.ItemTemplate>
|
||||
@@ -250,34 +242,23 @@
|
||||
</ListBox>
|
||||
</TabItem>
|
||||
|
||||
<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}">
|
||||
<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>
|
||||
|
||||
<!-- 2. Invia -->
|
||||
<TabItem Header="{Binding Loc[tab.send]}">
|
||||
<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}"
|
||||
FontFamily="monospace"/>
|
||||
<Grid ColumnDefinitions="*,Auto,Auto,Auto">
|
||||
@@ -302,6 +283,90 @@
|
||||
FontSize="13"/>
|
||||
</StackPanel>
|
||||
</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>
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Input.Platform;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.VisualTree;
|
||||
using PalladiumWallet.App.ViewModels;
|
||||
|
||||
namespace PalladiumWallet.App.Views;
|
||||
@@ -13,7 +18,6 @@ public partial class MainWindow : Window
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>File → Apri wallet da file (il picker richiede il TopLevel, da qui).</summary>
|
||||
private async void OnOpenWalletFileClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is not MainWindowViewModel vm)
|
||||
@@ -32,4 +36,26 @@ public partial class MainWindow : Window
|
||||
if (files.FirstOrDefault()?.TryGetLocalPath() is { } 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