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>
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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(); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1378,12 +1378,68 @@
|
|||||||
<TextBlock Text="{Binding Loc[help.title]}"
|
<TextBlock Text="{Binding Loc[help.title]}"
|
||||||
FontSize="18" FontWeight="Bold"/>
|
FontSize="18" FontWeight="Bold"/>
|
||||||
|
|
||||||
|
<TabControl>
|
||||||
|
<!-- Tab: Info -->
|
||||||
|
<TabItem Header="{Binding Loc[help.tab.info]}">
|
||||||
|
<StackPanel Spacing="12" Margin="0,12,0,0">
|
||||||
<StackPanel Spacing="4">
|
<StackPanel Spacing="4">
|
||||||
<TextBlock Text="Palladium Wallet" FontSize="16" FontWeight="Bold"/>
|
<TextBlock Text="Palladium Wallet" FontSize="16" FontWeight="Bold"/>
|
||||||
<TextBlock Text="{Binding WindowTitle}" FontSize="12" Foreground="{DynamicResource TextSecondaryBrush}"/>
|
<TextBlock Text="{Binding WindowTitle}" FontSize="12"
|
||||||
|
Foreground="{DynamicResource TextSecondaryBrush}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock Text="{Binding Loc[help.info]}" TextWrapping="Wrap"/>
|
||||||
|
</StackPanel>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<!-- Tab: Donate -->
|
||||||
|
<TabItem Header="{Binding Loc[help.tab.donate]}">
|
||||||
|
<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>
|
</StackPanel>
|
||||||
|
|
||||||
<TextBlock Text="{Binding Loc[help.info]}" TextWrapping="Wrap"/>
|
<!-- 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>
|
||||||
|
</TabItem>
|
||||||
|
</TabControl>
|
||||||
|
|
||||||
<Button Content="{Binding Loc[addr.close]}"
|
<Button Content="{Binding Loc[addr.close]}"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
|
|||||||
Reference in New Issue
Block a user