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:
@@ -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