3 Commits

Author SHA1 Message Date
davide 96b6a7e291 feat(ui): Help overlay with Info/Donate tabs, fixed dimensions
Help button now shows a fixed-size two-tab overlay:

Info tab: app name + version, one-line description, thin separator,
  © 2026 Davide Grilli — MIT License, Built with .NET 10 · Avalonia 12 · NBitcoin.

Donate tab: dev address (plm1qdq3…) read-only display, free amount
  input, prepare-preview row, confirm-and-broadcast button.

Overlay dimensions are stable across tabs and between desktop/mobile:
- TabControl Height=320 (fixed, never resizes on tab switch)
- HorizontalAlignment=Stretch + MaxWidth=440 on the card (always fills
  available width; previously Center caused the card to shrink to content
  width, which differed between Info and Donate)
- Each tab content wrapped in ScrollViewer for safe overflow handling

New MainWindowViewModel.Donate.cs: PrepareDonate / ConfirmDonate /
ResetDonate (called on overlay close). Six-language Loc keys added for
all donate strings.
2026-06-17 09:01:06 +02:00
davide b13b66160c feat(ui): donate tab in Help overlay
Help button now shows a two-tab overlay:
- Info: existing about/version content
- Donate: donate flow to the dev address (plm1qdq3…) with free amount
  input, prepare preview, and confirm-and-broadcast button

New MainWindowViewModel.Donate.cs partial: DonateAmount / DonatePreview
/ HasPendingDonate observable properties, PrepareDonateCommand (builds
the tx with hardcoded dev address via the existing TransactionFactory),
ConfirmDonateCommand (broadcasts and syncs). Donate state is reset on
overlay close. Six-language Loc keys added (help.tab.info/donate,
donate.desc/dev.address/amount/prepare/confirm).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 08:40:02 +02:00
davide 9744619eb4 fix(ui): mobile tab bar — uniform sizing, indicator overlap, spacing
- UniformGrid Columns="5" on TabStrip so all 5 tabs always stay on one
  row on Android (Rows="1" alone did not auto-calculate columns at
  measure time on the Android Avalonia renderer)
- Contacts tab header was using hardcoded FontSize=13/Width=24/Spacing=4
  instead of the TabFontSize/TabIconSize/TabSpacing ViewModel bindings,
  making it visually larger than the other four tabs on mobile
- ContentPresenter Margin="0,0,0,4" in Controls.axaml pushes the tab
  header content above the 2 px selection indicator, which is drawn at
  the absolute bottom of the template Panel and was overlapping the label
- TabItem horizontal padding 4→8 px and TabSpacing 2→4 for both
  desktop and mobile to give more breathing room between tab items
2026-06-17 08:17:56 +02:00
6 changed files with 367 additions and 119 deletions
+13
View File
@@ -60,6 +60,19 @@ public sealed class Loc
"Portefeuille SPV pour la cryptomonnaie Palladium (PLM).", "Portefeuille SPV pour la cryptomonnaie Palladium (PLM).",
"Carteira SPV para a criptomoeda Palladium (PLM).", "Carteira SPV para a criptomoeda Palladium (PLM).",
"SPV-Wallet für die Kryptowährung Palladium (PLM)."], "SPV-Wallet für die Kryptowährung Palladium (PLM)."],
["help.tab.info"] = ["Info", "Info", "Info", "Info", "Info", "Info"],
["help.tab.donate"] = ["Dona", "Donate", "Donar", "Faire un don", "Doar", "Spenden"],
["donate.desc"] = [
"Se questo wallet ti è utile, considera una piccola donazione allo sviluppatore.",
"If you find this wallet useful, consider a small donation to the developer.",
"Si esta wallet te resulta útil, considera una pequeña donación al desarrollador.",
"Si ce portefeuille vous est utile, envisagez un petit don au développeur.",
"Se esta carteira é útil para você, considere uma pequena doação ao desenvolvedor.",
"Wenn Ihnen dieses Wallet nützlich ist, erwägen Sie eine kleine Spende an den Entwickler."],
["donate.dev.address"] = ["Indirizzo sviluppatore", "Developer address", "Dirección del desarrollador", "Adresse du développeur", "Endereço do desenvolvedor", "Entwickleradresse"],
["donate.amount"] = ["Importo donazione", "Donation amount", "Monto de donación", "Montant du don", "Valor da doação", "Spendenbetrag"],
["donate.prepare"] = ["Prepara donazione", "Prepare donation", "Preparar donación", "Préparer le don", "Preparar doação", "Spende vorbereiten"],
["donate.confirm"] = ["Conferma e invia", "Confirm and send", "Confirmar y enviar", "Confirmer et envoyer", "Confirmar e enviar", "Bestätigen und senden"],
["settings.unit.short"] = ["Unità", "Unit", "Unidad", "Unité", "Unidade", "Einheit"], ["settings.unit.short"] = ["Unità", "Unit", "Unidad", "Unité", "Unidade", "Einheit"],
// Wizard // Wizard
+5
View File
@@ -117,6 +117,11 @@
<Style Selector="TabItem:selected /template/ ContentPresenter#PART_ContentPresenter"> <Style Selector="TabItem:selected /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextElement.Foreground" Value="{DynamicResource PrimaryBrush}"/> <Setter Property="TextElement.Foreground" Value="{DynamicResource PrimaryBrush}"/>
</Style> </Style>
<!-- Push tab header content up so the 2px selection indicator (drawn at Panel bottom)
does not overlap the text label. -->
<Style Selector="TabItem /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Margin" Value="0,0,0,4"/>
</Style>
<!-- ===== Reusable classes ===== --> <!-- ===== Reusable classes ===== -->
<!-- Card: elevated surface with a subtle border and soft corners --> <!-- Card: elevated surface with a subtle border and soft corners -->
@@ -0,0 +1,96 @@
using System;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NBitcoin;
using PalladiumWallet.Core.Chain;
using PalladiumWallet.Core.Net;
using PalladiumWallet.Core.Wallet;
namespace PalladiumWallet.App.ViewModels;
public partial class MainWindowViewModel
{
public const string DevAddress = "plm1qdq3gu2zvg9lyr8gxd6yln4wavc5tlp8prmvfay";
[ObservableProperty]
private string donateAmount = "";
[ObservableProperty]
private string donatePreview = "";
[ObservableProperty]
private bool hasPendingDonate;
private BuiltTransaction? _pendingDonate;
[RelayCommand]
private async Task PrepareDonate()
{
_pendingDonate = null;
HasPendingDonate = false;
if (_account is null || _doc?.Cache is null || _lastTransactions is null)
{
DonatePreview = "Sincronizza prima di inviare.";
return;
}
try
{
var destination = BitcoinAddress.Create(DevAddress, PalladiumNetworks.For(Net));
if (!CoinAmount.TryParseIn(DonateAmount.Trim(), _config.Unit, out var amount) || amount <= 0)
{
DonatePreview = "Importo non valido.";
return;
}
if (!decimal.TryParse(SendFeeRate, System.Globalization.NumberStyles.Any,
System.Globalization.CultureInfo.InvariantCulture, out var feeRate) || feeRate <= 0)
feeRate = 1m;
_pendingDonate = new TransactionFactory(_account).Build(
_doc.Cache.Utxos, _lastTransactions, destination, amount, feeRate,
_doc.Cache.NextChangeIndex, sendAll: false);
DonatePreview = $"txid {_pendingDonate.Txid[..16]}… · " +
$"fee {Fmt(_pendingDonate.Fee.Satoshi)} " +
$"({_pendingDonate.Transaction.GetVirtualSize()} vB)" +
(_pendingDonate.Signed ? "" : " · watch-only");
HasPendingDonate = _pendingDonate.Signed;
}
catch (Exception ex)
{
_pendingDonate = null;
HasPendingDonate = false;
DonatePreview = $"Errore: {ex.Message}";
}
await Task.CompletedTask;
}
[RelayCommand]
private async Task ConfirmDonate()
{
if (_pendingDonate is null || _client is null)
return;
try
{
var txid = await _client.BroadcastAsync(_pendingDonate.ToHex());
DonatePreview = $"Grazie! txid: {txid}";
DonateAmount = "";
_pendingDonate = null;
HasPendingDonate = false;
await ConnectAndSync();
}
catch (Exception ex)
{
DonatePreview = $"Errore broadcast: {ex.Message}";
}
}
private void ResetDonate()
{
DonateAmount = "";
DonatePreview = "";
HasPendingDonate = false;
_pendingDonate = null;
}
}
@@ -70,5 +70,5 @@ public partial class MainWindowViewModel
private void OpenHelp() => IsHelpOpen = true; private void OpenHelp() => IsHelpOpen = true;
[RelayCommand] [RelayCommand]
private void CloseHelp() => IsHelpOpen = false; private void CloseHelp() { IsHelpOpen = false; ResetDonate(); }
} }
@@ -96,6 +96,11 @@ public partial class MainWindowViewModel : ViewModelBase
public bool IsMobile => !IsDesktop; public bool IsMobile => !IsDesktop;
// Tab bar sizing: compact on mobile so all 5 tabs fit in one row.
public double TabIconSize => IsMobile ? 20 : 24;
public double TabFontSize => IsMobile ? 10 : 13;
public double TabSpacing => IsMobile ? 4 : 4;
public string UnitLabel => _config.Unit; public string UnitLabel => _config.Unit;
public AppConfig CurrentConfig => _config; public AppConfig CurrentConfig => _config;
+247 -118
View File
@@ -324,7 +324,7 @@
<Setter Property="Background" Value="{DynamicResource SurfaceBrush}"/> <Setter Property="Background" Value="{DynamicResource SurfaceBrush}"/>
<Setter Property="ItemsPanel"> <Setter Property="ItemsPanel">
<ItemsPanelTemplate> <ItemsPanelTemplate>
<UniformGrid Rows="1"/> <UniformGrid Rows="1" Columns="5"/>
</ItemsPanelTemplate> </ItemsPanelTemplate>
</Setter> </Setter>
</Style> </Style>
@@ -332,24 +332,27 @@
<Setter Property="HorizontalAlignment" Value="Stretch"/> <Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/> <Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="8,14"/> <Setter Property="Padding" Value="8,12"/>
<Setter Property="MinWidth" Value="80"/>
</Style> </Style>
</TabControl.Styles> </TabControl.Styles>
<!-- 1. History --> <!-- 1. History -->
<TabItem> <TabItem>
<TabItem.Header> <TabItem.Header>
<StackPanel Spacing="4" HorizontalAlignment="Center"> <StackPanel Spacing="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabSpacing}"
<Viewbox Width="24" Height="24" HorizontalAlignment="Center"> HorizontalAlignment="Center">
<Canvas Width="24" Height="24"> <Viewbox Width="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabIconSize}"
<Path Fill="{Binding $parent[TabItem].Foreground}" Height="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabIconSize}"
Data="M7,5H21V7H7V5M7,13V11H21V13H7M4,4.5A1.5,1.5 0 0,1 5.5,6A1.5,1.5 0 0,1 4,7.5A1.5,1.5 0 0,1 2.5,6A1.5,1.5 0 0,1 4,4.5M4,10.5A1.5,1.5 0 0,1 5.5,12A1.5,1.5 0 0,1 4,13.5A1.5,1.5 0 0,1 2.5,12A1.5,1.5 0 0,1 4,10.5M7,19V17H21V19H7M4,16.5A1.5,1.5 0 0,1 5.5,18A1.5,1.5 0 0,1 4,19.5A1.5,1.5 0 0,1 2.5,18A1.5,1.5 0 0,1 4,16.5Z"/> HorizontalAlignment="Center">
</Canvas> <Canvas Width="24" Height="24">
</Viewbox> <Path Fill="{Binding $parent[TabItem].Foreground}"
<TextBlock Text="{Binding Loc[tab.history]}" FontSize="13" Data="M7,5H21V7H7V5M7,13V11H21V13H7M4,4.5A1.5,1.5 0 0,1 5.5,6A1.5,1.5 0 0,1 4,7.5A1.5,1.5 0 0,1 2.5,6A1.5,1.5 0 0,1 4,4.5M4,10.5A1.5,1.5 0 0,1 5.5,12A1.5,1.5 0 0,1 4,13.5A1.5,1.5 0 0,1 2.5,12A1.5,1.5 0 0,1 4,10.5M7,19V17H21V19H7M4,16.5A1.5,1.5 0 0,1 5.5,18A1.5,1.5 0 0,1 4,19.5A1.5,1.5 0 0,1 2.5,18A1.5,1.5 0 0,1 4,16.5Z"/>
HorizontalAlignment="Center"/> </Canvas>
</StackPanel> </Viewbox>
<TextBlock Text="{Binding Loc[tab.history]}"
FontSize="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabFontSize}"
HorizontalAlignment="Center"/>
</StackPanel>
</TabItem.Header> </TabItem.Header>
<Grid RowDefinitions="Auto,*" Margin="4"> <Grid RowDefinitions="Auto,*" Margin="4">
<TextBlock Grid.Row="0" Text="{Binding Loc[history.hint]}" <TextBlock Grid.Row="0" Text="{Binding Loc[history.hint]}"
@@ -393,16 +396,20 @@
<!-- 2. Send --> <!-- 2. Send -->
<TabItem> <TabItem>
<TabItem.Header> <TabItem.Header>
<StackPanel Spacing="4" HorizontalAlignment="Center"> <StackPanel Spacing="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabSpacing}"
<Viewbox Width="24" Height="24" HorizontalAlignment="Center"> HorizontalAlignment="Center">
<Canvas Width="24" Height="24"> <Viewbox Width="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabIconSize}"
<Path Fill="{Binding $parent[TabItem].Foreground}" Height="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabIconSize}"
Data="M2,21L23,12L2,3V10L17,12L2,14V21Z"/> HorizontalAlignment="Center">
</Canvas> <Canvas Width="24" Height="24">
</Viewbox> <Path Fill="{Binding $parent[TabItem].Foreground}"
<TextBlock Text="{Binding Loc[tab.send]}" FontSize="13" Data="M2,21L23,12L2,3V10L17,12L2,14V21Z"/>
HorizontalAlignment="Center"/> </Canvas>
</StackPanel> </Viewbox>
<TextBlock Text="{Binding Loc[tab.send]}"
FontSize="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabFontSize}"
HorizontalAlignment="Center"/>
</StackPanel>
</TabItem.Header> </TabItem.Header>
<ScrollViewer> <ScrollViewer>
<Panel Margin="16,14"> <Panel Margin="16,14">
@@ -496,62 +503,87 @@
</Grid> </Grid>
<!-- ── MOBILE: vertical stack ── --> <!-- ── MOBILE: vertical stack ── -->
<StackPanel IsVisible="{Binding IsMobile}" Spacing="14"> <StackPanel IsVisible="{Binding IsMobile}" Spacing="12">
<Border Classes="card" Padding="20,16">
<StackPanel Spacing="12"> <!-- Recipient card with labelled inputs -->
<Border Classes="card" Padding="20,18">
<StackPanel Spacing="14">
<TextBlock Text="{Binding Loc[send.sect.recipient]}" Classes="section"/> <TextBlock Text="{Binding Loc[send.sect.recipient]}" Classes="section"/>
<Grid ColumnDefinitions="Auto,*"> <!-- Contact picker -->
<TextBlock Grid.Column="0" Text="{Binding Loc[send.from.contact]}" <StackPanel Spacing="4">
VerticalAlignment="Center" Margin="0,0,10,0" <TextBlock Text="{Binding Loc[send.from.contact]}" Classes="label"/>
Foreground="{DynamicResource TextSecondaryBrush}" FontSize="13"/> <ComboBox HorizontalAlignment="Stretch"
<ComboBox Grid.Column="1" HorizontalAlignment="Stretch"
ItemsSource="{Binding Contacts}" ItemsSource="{Binding Contacts}"
SelectedItem="{Binding SendToContact}" SelectedItem="{Binding SendToContact}"
PlaceholderText="{Binding Loc[send.contact.hint]}"> PlaceholderText="{Binding Loc[send.contact.hint]}"
MinHeight="48">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:ContactEntry"> <DataTemplate x:DataType="vm:ContactEntry">
<TextBlock Text="{Binding Name}"/> <TextBlock Text="{Binding Name}" FontSize="15"/>
</DataTemplate> </DataTemplate>
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
</Grid> </StackPanel>
<Grid ColumnDefinitions="*,Auto"> <!-- Address + QR scan button -->
<TextBox Grid.Column="0" <StackPanel Spacing="4">
PlaceholderText="{Binding Loc[send.to]}" <TextBlock Text="{Binding Loc[send.to]}" Classes="label"/>
Text="{Binding SendTo}" <Grid ColumnDefinitions="*,Auto">
FontFamily="monospace"/> <TextBox Grid.Column="0"
<Button Grid.Column="1" Margin="8,0,0,0" Padding="10,6" PlaceholderText="{Binding Loc[send.to]}"
Command="{Binding ScanQrCommand}" Text="{Binding SendTo}"
ToolTip.Tip="{Binding Loc[send.scan]}"> FontFamily="monospace" FontSize="15"
<Viewbox Width="20" Height="20"> MinHeight="48"/>
<Canvas Width="24" Height="24"> <Button Grid.Column="1" Margin="8,0,0,0"
<Path Fill="{DynamicResource SystemBaseHighColor}" Width="52" Height="48"
Data="M20,5H16.83L15,3H9L7.17,5H4A2,2 0 0,0 2,7V19A2,2 0 0,0 4,21H20A2,2 0 0,0 22,19V7A2,2 0 0,0 20,5M20,19H4V7H8.05L9.88,5H14.12L15.95,7H20V19M12,8A5,5 0 0,0 7,13A5,5 0 0,0 12,18A5,5 0 0,0 17,13A5,5 0 0,0 12,8M12,16A3,3 0 0,1 9,13A3,3 0 0,1 12,10A3,3 0 0,1 15,13A3,3 0 0,1 12,16Z"/> Command="{Binding ScanQrCommand}"
</Canvas> ToolTip.Tip="{Binding Loc[send.scan]}">
</Viewbox> <Viewbox Width="22" Height="22">
</Button> <Canvas Width="24" Height="24">
</Grid> <Path Fill="{DynamicResource SystemBaseHighColor}"
</StackPanel> Data="M20,5H16.83L15,3H9L7.17,5H4A2,2 0 0,0 2,7V19A2,2 0 0,0 4,21H20A2,2 0 0,0 22,19V7A2,2 0 0,0 20,5M20,19H4V7H8.05L9.88,5H14.12L15.95,7H20V19M12,8A5,5 0 0,0 7,13A5,5 0 0,0 12,18A5,5 0 0,0 17,13A5,5 0 0,0 12,8M12,16A3,3 0 0,1 9,13A3,3 0 0,1 12,10A3,3 0 0,1 15,13A3,3 0 0,1 12,16Z"/>
</Border> </Canvas>
<Border Classes="card" Padding="20,16"> </Viewbox>
<StackPanel Spacing="12"> </Button>
<TextBlock Text="{Binding Loc[send.sect.amount]}" Classes="section"/> </Grid>
<Grid ColumnDefinitions="*,Auto,Auto">
<TextBox Grid.Column="0" PlaceholderText="0.00000000"
Text="{Binding SendAmount}" IsEnabled="{Binding !SendAll}"
FontFamily="monospace"/>
<TextBlock Grid.Column="1" Text="{Binding UnitLabel}"
VerticalAlignment="Center" Margin="8,0,0,0"
Foreground="{DynamicResource TextSecondaryBrush}"/>
<CheckBox Grid.Column="2" Content="{Binding Loc[send.all]}"
IsChecked="{Binding SendAll}" Margin="10,0,0,0"/>
</Grid>
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="{Binding Loc[send.feerate]}" VerticalAlignment="Center" Classes="label"/>
<TextBox Text="{Binding SendFeeRate}" MinWidth="80" FontFamily="monospace"/>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Border> </Border>
<!-- Amount & fee card with labelled inputs -->
<Border Classes="card" Padding="20,18">
<StackPanel Spacing="14">
<TextBlock Text="{Binding Loc[send.sect.amount]}" Classes="section"/>
<!-- Amount field -->
<StackPanel Spacing="4">
<TextBlock Text="{Binding Loc[send.amount]}" Classes="label"/>
<Grid ColumnDefinitions="*,Auto">
<TextBox Grid.Column="0"
PlaceholderText="0.00000000"
Text="{Binding SendAmount}"
IsEnabled="{Binding !SendAll}"
FontFamily="monospace" FontSize="16"
MinHeight="48"/>
<TextBlock Grid.Column="1" Text="{Binding UnitLabel}"
VerticalAlignment="Center" Margin="12,0,0,0"
FontWeight="Medium"
Foreground="{DynamicResource TextSecondaryBrush}"/>
</Grid>
</StackPanel>
<!-- Send-all toggle -->
<CheckBox Content="{Binding Loc[send.all]}"
IsChecked="{Binding SendAll}"
MinHeight="44" FontSize="14"/>
<!-- Fee rate field -->
<StackPanel Spacing="4">
<TextBlock Text="{Binding Loc[send.feerate]}" Classes="label"/>
<TextBox Text="{Binding SendFeeRate}"
FontFamily="monospace" FontSize="15"
MinHeight="48" HorizontalAlignment="Stretch"/>
</StackPanel>
</StackPanel>
</Border>
<!-- Summary card -->
<Border Classes="card" Padding="20,16" <Border Classes="card" Padding="20,16"
IsVisible="{Binding SendPreview, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"> IsVisible="{Binding SendPreview, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<StackPanel Spacing="8"> <StackPanel Spacing="8">
@@ -561,15 +593,19 @@
Foreground="{DynamicResource TextSecondaryBrush}"/> Foreground="{DynamicResource TextSecondaryBrush}"/>
</StackPanel> </StackPanel>
</Border> </Border>
<Grid ColumnDefinitions="*,*" ColumnSpacing="12">
<Button Grid.Column="0" Content="{Binding Loc[send.prepare]}" <!-- Action buttons: primary (Confirm) prominent, secondary below -->
Command="{Binding PrepareSendCommand}" <Button Content="{Binding Loc[send.confirm]}" Classes="accent"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"/> Command="{Binding ConfirmSendCommand}"
<Button Grid.Column="1" Content="{Binding Loc[send.confirm]}" Classes="accent" IsEnabled="{Binding HasPendingSend}"
Command="{Binding ConfirmSendCommand}" MinHeight="52"
IsEnabled="{Binding HasPendingSend}" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"/> FontSize="15"/>
</Grid> <Button Content="{Binding Loc[send.prepare]}"
Command="{Binding PrepareSendCommand}"
MinHeight="48"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"/>
</StackPanel> </StackPanel>
</Panel> </Panel>
@@ -579,16 +615,20 @@
<!-- 3. Receive --> <!-- 3. Receive -->
<TabItem> <TabItem>
<TabItem.Header> <TabItem.Header>
<StackPanel Spacing="4" HorizontalAlignment="Center"> <StackPanel Spacing="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabSpacing}"
<Viewbox Width="24" Height="24" HorizontalAlignment="Center"> HorizontalAlignment="Center">
<Canvas Width="24" Height="24"> <Viewbox Width="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabIconSize}"
<Path Fill="{Binding $parent[TabItem].Foreground}" Height="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabIconSize}"
Data="M2,12H4V17H20V12H22V17A2,2 0 0,1 20,19H4A2,2 0 0,1 2,17V12M12,15L17,10L15.59,8.58L13,11.17V2H11V11.17L8.41,8.59L7,10L12,15Z"/> HorizontalAlignment="Center">
</Canvas> <Canvas Width="24" Height="24">
</Viewbox> <Path Fill="{Binding $parent[TabItem].Foreground}"
<TextBlock Text="{Binding Loc[tab.receive]}" FontSize="13" Data="M2,12H4V17H20V12H22V17A2,2 0 0,1 20,19H4A2,2 0 0,1 2,17V12M12,15L17,10L15.59,8.58L13,11.17V2H11V11.17L8.41,8.59L7,10L12,15Z"/>
HorizontalAlignment="Center"/> </Canvas>
</StackPanel> </Viewbox>
<TextBlock Text="{Binding Loc[tab.receive]}"
FontSize="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabFontSize}"
HorizontalAlignment="Center"/>
</StackPanel>
</TabItem.Header> </TabItem.Header>
<ScrollViewer> <ScrollViewer>
<Panel Margin="16,14"> <Panel Margin="16,14">
@@ -635,39 +675,49 @@
</Grid> </Grid>
<!-- ── MOBILE: vertical stack ── --> <!-- ── MOBILE: vertical stack ── -->
<StackPanel IsVisible="{Binding IsMobile}" Spacing="14"> <StackPanel IsVisible="{Binding IsMobile}" Spacing="12">
<Border Classes="card" Padding="24,20"
<!-- QR card: full-width, centered with generous padding -->
<Border Classes="card" Padding="24,22"
IsVisible="{Binding ReceiveQr, Converter={x:Static ObjectConverters.IsNotNull}}"> IsVisible="{Binding ReceiveQr, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Spacing="18" HorizontalAlignment="Center"> <StackPanel Spacing="16" HorizontalAlignment="Center">
<TextBlock Text="{Binding Loc[receive.next]}" Classes="label" <TextBlock Text="{Binding Loc[receive.next]}" Classes="label"
HorizontalAlignment="Center"/> HorizontalAlignment="Center" FontSize="13"/>
<Border Background="White" Padding="14" CornerRadius="10" <Border Background="White" Padding="16" CornerRadius="12"
HorizontalAlignment="Center"> HorizontalAlignment="Center">
<Image Source="{Binding ReceiveQr}" Width="200" Height="200" <Image Source="{Binding ReceiveQr}" Width="220" Height="220"
RenderOptions.BitmapInterpolationMode="None"/> RenderOptions.BitmapInterpolationMode="None"/>
</Border> </Border>
</StackPanel> </StackPanel>
</Border> </Border>
<Border Classes="card" Padding="20,18">
<StackPanel Spacing="12"> <!-- Address card: label + monospace box + big Copy button -->
<Border Classes="card" Padding="20,20">
<StackPanel Spacing="14">
<TextBlock Text="{Binding Loc[receive.your.address]}" Classes="section"/> <TextBlock Text="{Binding Loc[receive.your.address]}" Classes="section"/>
<Border Background="{DynamicResource SurfaceAltBrush}" <Border Background="{DynamicResource SurfaceAltBrush}"
CornerRadius="8" Padding="14,10"> CornerRadius="8" Padding="16,12">
<SelectableTextBlock Text="{Binding ReceiveAddress}" <SelectableTextBlock Text="{Binding ReceiveAddress}"
FontFamily="monospace" FontSize="13" FontFamily="monospace" FontSize="14"
TextWrapping="Wrap" TextWrapping="Wrap"
TextAlignment="Center" TextAlignment="Center"
HorizontalAlignment="Center"/> HorizontalAlignment="Center"
LineHeight="22"/>
</Border> </Border>
<Button Content="{Binding Loc[receive.copy]}" Classes="accent" <Button Content="{Binding Loc[receive.copy]}" Classes="accent"
Click="OnCopyReceiveAddressClick" Click="OnCopyReceiveAddressClick"
MinHeight="52"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"/> HorizontalContentAlignment="Center"
FontSize="15"/>
</StackPanel> </StackPanel>
</Border> </Border>
<!-- Hint -->
<TextBlock Text="{Binding Loc[receive.hint]}" <TextBlock Text="{Binding Loc[receive.hint]}"
Foreground="{DynamicResource TextSecondaryBrush}" FontSize="12" Foreground="{DynamicResource TextSecondaryBrush}" FontSize="12"
TextWrapping="Wrap" TextAlignment="Center" Margin="4,0"/> TextWrapping="Wrap" TextAlignment="Center" Margin="4,0"/>
</StackPanel> </StackPanel>
</Panel> </Panel>
@@ -677,16 +727,20 @@
<!-- 4. Addresses --> <!-- 4. Addresses -->
<TabItem> <TabItem>
<TabItem.Header> <TabItem.Header>
<StackPanel Spacing="4" HorizontalAlignment="Center"> <StackPanel Spacing="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabSpacing}"
<Viewbox Width="24" Height="24" HorizontalAlignment="Center"> HorizontalAlignment="Center">
<Canvas Width="24" Height="24"> <Viewbox Width="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabIconSize}"
<Path Fill="{Binding $parent[TabItem].Foreground}" Height="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabIconSize}"
Data="M21,18V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5A2,2 0 0,1 5,3H19A2,2 0 0,1 21,5V6H12C10.89,6 10,6.9 10,8V16A2,2 0 0,0 12,18H21M12,16H22V8H12V16M16,13.5A1.5,1.5 0 0,1 14.5,12A1.5,1.5 0 0,1 16,10.5A1.5,1.5 0 0,1 17.5,12A1.5,1.5 0 0,1 16,13.5Z"/> HorizontalAlignment="Center">
</Canvas> <Canvas Width="24" Height="24">
</Viewbox> <Path Fill="{Binding $parent[TabItem].Foreground}"
<TextBlock Text="{Binding Loc[tab.addresses]}" FontSize="13" Data="M21,18V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5A2,2 0 0,1 5,3H19A2,2 0 0,1 21,5V6H12C10.89,6 10,6.9 10,8V16A2,2 0 0,0 12,18H21M12,16H22V8H12V16M16,13.5A1.5,1.5 0 0,1 14.5,12A1.5,1.5 0 0,1 16,10.5A1.5,1.5 0 0,1 17.5,12A1.5,1.5 0 0,1 16,13.5Z"/>
HorizontalAlignment="Center"/> </Canvas>
</StackPanel> </Viewbox>
<TextBlock Text="{Binding Loc[tab.addresses]}"
FontSize="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabFontSize}"
HorizontalAlignment="Center"/>
</StackPanel>
</TabItem.Header> </TabItem.Header>
<Grid RowDefinitions="Auto,*" Margin="4"> <Grid RowDefinitions="Auto,*" Margin="4">
<!-- Column header — desktop only --> <!-- Column header — desktop only -->
@@ -739,14 +793,18 @@
<!-- 5. Contacts --> <!-- 5. Contacts -->
<TabItem> <TabItem>
<TabItem.Header> <TabItem.Header>
<StackPanel Spacing="4" HorizontalAlignment="Center"> <StackPanel Spacing="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabSpacing}"
<Viewbox Width="24" Height="24" HorizontalAlignment="Center"> HorizontalAlignment="Center">
<Viewbox Width="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabIconSize}"
Height="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabIconSize}"
HorizontalAlignment="Center">
<Canvas Width="24" Height="24"> <Canvas Width="24" Height="24">
<Path Fill="{Binding $parent[TabItem].Foreground}" <Path Fill="{Binding $parent[TabItem].Foreground}"
Data="M16,13C15.71,13 15.38,13 15.03,13.05C16.19,13.89 17,15 17,16.5V19H23V16.5C23,14.17 18.33,13 16,13M8,13C5.67,13 1,14.17 1,16.5V19H15V16.5C15,14.17 10.33,13 8,13M8,11A3,3 0 0,0 11,8A3,3 0 0,0 8,5A3,3 0 0,0 5,8A3,3 0 0,0 8,11M16,11A3,3 0 0,0 19,8A3,3 0 0,0 16,5C15.71,5 15.42,5.03 15.14,5.09C15.68,5.95 16,6.94 16,8C16,9.06 15.68,10.05 15.14,10.91C15.42,10.97 15.71,11 16,11Z"/> Data="M16,13C15.71,13 15.38,13 15.03,13.05C16.19,13.89 17,15 17,16.5V19H23V16.5C23,14.17 18.33,13 16,13M8,13C5.67,13 1,14.17 1,16.5V19H15V16.5C15,14.17 10.33,13 8,13M8,11A3,3 0 0,0 11,8A3,3 0 0,0 8,5A3,3 0 0,0 5,8A3,3 0 0,0 8,11M16,11A3,3 0 0,0 19,8A3,3 0 0,0 16,5C15.71,5 15.42,5.03 15.14,5.09C15.68,5.95 16,6.94 16,8C16,9.06 15.68,10.05 15.14,10.91C15.42,10.97 15.71,11 16,11Z"/>
</Canvas> </Canvas>
</Viewbox> </Viewbox>
<TextBlock Text="{Binding Loc[tab.contacts]}" FontSize="13" <TextBlock Text="{Binding Loc[tab.contacts]}"
FontSize="{Binding $parent[TabControl].((vm:MainWindowViewModel)DataContext).TabFontSize}"
HorizontalAlignment="Center"/> HorizontalAlignment="Center"/>
</StackPanel> </StackPanel>
</TabItem.Header> </TabItem.Header>
@@ -1315,17 +1373,88 @@
<Border Background="{DynamicResource OverlayCardBrush}" <Border Background="{DynamicResource OverlayCardBrush}"
BorderBrush="{DynamicResource BorderSubtleBrush}" BorderThickness="1" CornerRadius="8" BorderBrush="{DynamicResource BorderSubtleBrush}" BorderThickness="1" CornerRadius="8"
MaxWidth="440" Margin="16" MaxWidth="440" Margin="16"
HorizontalAlignment="Center" VerticalAlignment="Center"> HorizontalAlignment="Stretch" VerticalAlignment="Center">
<StackPanel Margin="24" Spacing="16"> <StackPanel Margin="24" Spacing="16">
<TextBlock Text="{Binding Loc[help.title]}" <TextBlock Text="{Binding Loc[help.title]}"
FontSize="18" FontWeight="Bold"/> FontSize="18" FontWeight="Bold"/>
<StackPanel Spacing="4"> <!-- Fixed height so the overlay never resizes when switching tabs. -->
<TextBlock Text="Palladium Wallet" FontSize="16" FontWeight="Bold"/> <TabControl Height="320">
<TextBlock Text="{Binding WindowTitle}" FontSize="12" Foreground="{DynamicResource TextSecondaryBrush}"/> <!-- Tab: Info -->
</StackPanel> <TabItem Header="{Binding Loc[help.tab.info]}">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="12" Margin="0,12,0,0">
<StackPanel Spacing="4">
<TextBlock Text="Palladium Wallet" FontSize="16" FontWeight="Bold"/>
<TextBlock Text="{Binding WindowTitle}" FontSize="12"
Foreground="{DynamicResource TextSecondaryBrush}"/>
</StackPanel>
<TextBlock Text="{Binding Loc[help.info]}" TextWrapping="Wrap"/>
<Border BorderBrush="{DynamicResource BorderSubtleBrush}"
BorderThickness="0,1,0,0" Margin="0,4,0,0"/>
<StackPanel Spacing="4">
<TextBlock Text="© 2026 Davide Grilli — MIT License"
FontSize="12"
Foreground="{DynamicResource TextSecondaryBrush}"/>
<TextBlock Text="Built with .NET 10 · Avalonia 12 · NBitcoin"
FontSize="11"
Foreground="{DynamicResource TextSecondaryBrush}"/>
</StackPanel>
</StackPanel>
</ScrollViewer>
</TabItem>
<TextBlock Text="{Binding Loc[help.info]}" TextWrapping="Wrap"/> <!-- Tab: Donate -->
<TabItem Header="{Binding Loc[help.tab.donate]}">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="12" Margin="0,12,0,0">
<TextBlock Text="{Binding Loc[donate.desc]}" TextWrapping="Wrap"
Foreground="{DynamicResource TextSecondaryBrush}"/>
<!-- Dev address (read-only) -->
<StackPanel Spacing="4">
<TextBlock Text="{Binding Loc[donate.dev.address]}" FontSize="11"
Foreground="{DynamicResource TextSecondaryBrush}"/>
<SelectableTextBlock Text="{x:Static vm:MainWindowViewModel.DevAddress}"
FontFamily="monospace" FontSize="11"
TextWrapping="Wrap"/>
</StackPanel>
<!-- Amount input -->
<StackPanel Spacing="4">
<TextBlock Text="{Binding Loc[donate.amount]}" FontSize="11"
Foreground="{DynamicResource TextSecondaryBrush}"/>
<Grid ColumnDefinitions="*,Auto">
<TextBox Grid.Column="0"
Text="{Binding DonateAmount}"
PlaceholderText="{Binding UnitLabel}"/>
<TextBlock Grid.Column="1" Text="{Binding UnitLabel}"
VerticalAlignment="Center" Margin="8,0,0,0"
Foreground="{DynamicResource TextSecondaryBrush}"/>
</Grid>
</StackPanel>
<!-- Preview -->
<SelectableTextBlock Text="{Binding DonatePreview}"
IsVisible="{Binding DonatePreview,
Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
FontFamily="monospace" FontSize="11"
TextWrapping="Wrap"
Foreground="{DynamicResource TextSecondaryBrush}"/>
<!-- Buttons -->
<StackPanel Orientation="Horizontal" Spacing="8" HorizontalAlignment="Right">
<Button Content="{Binding Loc[donate.confirm]}"
Classes="accent"
IsEnabled="{Binding HasPendingDonate}"
Command="{Binding ConfirmDonateCommand}"/>
<Button Content="{Binding Loc[donate.prepare]}"
Command="{Binding PrepareDonateCommand}"/>
</StackPanel>
</StackPanel>
</ScrollViewer>
</TabItem>
</TabControl>
<Button Content="{Binding Loc[addr.close]}" <Button Content="{Binding Loc[addr.close]}"
HorizontalAlignment="Right" HorizontalAlignment="Right"