feat(app): server settings as in-app overlay, split host/port fields

- Moves server configuration from the wallet panel into an overlay
  (Impostazioni → Server), same pattern as the address detail overlay
- Splits the single "host:port" text box into separate Host and Port
  fields; port changes auto-toggle the TLS checkbox when the typed
  port matches a known SSL/TCP port, and vice-versa
- Adds DisconnectAsync() so changing server/TLS reconnects to the new
  endpoint instead of reusing the old socket
- Adds i18n keys (IT/EN/ES/FR/PT/DE) for the new overlay
- Removes the server row from the main wallet panel; connection status
  indicator (green/red dot) is now shown in the wallet header
This commit is contained in:
2026-06-12 09:02:14 +02:00
parent cf6e2d7654
commit 28cb4ce6ae
4 changed files with 219 additions and 57 deletions
+8
View File
@@ -243,5 +243,13 @@ public sealed class Loc
["settings.unit"] = ["Unità degli importi", "Amount unit", "Unidad de importes", "Unité des montants", "Unidade dos valores", "Betrageinheit"], ["settings.unit"] = ["Unità degli importi", "Amount unit", "Unidad de importes", "Unité des montants", "Unidade dos valores", "Betrageinheit"],
["settings.ok"] = ["Salva", "Save", "Guardar", "Enregistrer", "Salvar", "Speichern"], ["settings.ok"] = ["Salva", "Save", "Guardar", "Enregistrer", "Salvar", "Speichern"],
["settings.cancel"] = ["Annulla", "Cancel", "Cancelar", "Annuler", "Cancelar", "Abbrechen"], ["settings.cancel"] = ["Annulla", "Cancel", "Cancelar", "Annuler", "Cancelar", "Abbrechen"],
["settings.server"] = ["Server di indicizzazione…", "Indexing server…", "Servidor de indexación…", "Serveur d'indexation…", "Servidor de indexação…", "Indexierungsserver…"],
// Finestra server
["server.title"] = ["Server di indicizzazione", "Indexing server", "Servidor de indexación", "Serveur d'indexation", "Servidor de indexação", "Indexierungsserver"],
["server.host"] = ["Host", "Host", "Host", "Hôte", "Host", "Host"],
["server.port"] = ["Porta", "Port", "Puerto", "Port", "Porta", "Port"],
["server.known"] = ["Server conosciuti (clicca per usarlo):", "Known servers (click to use):", "Servidores conocidos (clic para usar):", "Serveurs connus (cliquez pour utiliser) :", "Servidores conhecidos (clique para usar):", "Bekannte Server (zum Verwenden anklicken):"],
["server.empty"] = ["Nessun server conosciuto. Usa «Cerca altri server» dopo esserti connesso.", "No known servers. Use “Discover servers” after connecting.", "No hay servidores conocidos. Usa «Buscar otros servidores» tras conectar.", "Aucun serveur connu. Utilisez « Rechercher des serveurs » après connexion.", "Nenhum servidor conhecido. Use «Procurar servidores» após conectar.", "Keine bekannten Server. Nutzen Sie „Server suchen“ nach dem Verbinden."],
}; };
} }
+91 -11
View File
@@ -221,10 +221,25 @@ public partial class MainWindowViewModel : ViewModelBase
private string receiveAddress = ""; private string receiveAddress = "";
[ObservableProperty] [ObservableProperty]
private string serverInput = ""; private string serverHost = "";
[ObservableProperty] [ObservableProperty]
private bool useSsl; private string serverPort = "";
/// <summary>Si predilige TLS: la connessione automatica all'apertura del
/// wallet usa la porta SSL del server. L'utente può disattivarlo.</summary>
[ObservableProperty]
private bool useSsl = true;
/// <summary>Overlay impostazioni server: aperto da menu Impostazioni → Server.</summary>
[ObservableProperty]
private bool isServerSettingsOpen;
[RelayCommand]
private void OpenServerSettings() => IsServerSettingsOpen = true;
[RelayCommand]
private void CloseServerSettings() => IsServerSettingsOpen = false;
[ObservableProperty] [ObservableProperty]
private string connectionStatus = Loc.Tr("conn.none"); private string connectionStatus = Loc.Tr("conn.none");
@@ -401,19 +416,59 @@ public partial class MainWindowViewModel : ViewModelBase
KnownServers.Add(server); KnownServers.Add(server);
SelectedKnownServer = KnownServers.FirstOrDefault(); SelectedKnownServer = KnownServers.FirstOrDefault();
if (SelectedKnownServer is null) if (SelectedKnownServer is null)
ServerInput = $"127.0.0.1:{Profile.DefaultTcpPort}"; {
ServerHost = "127.0.0.1";
ServerPort = (UseSsl ? Profile.DefaultSslPort : Profile.DefaultTcpPort).ToString();
} }
}
/// <summary>Evita la ricorsione tra OnUseSslChanged e OnServerPortChanged
/// mentre tengono coerenti porta e flag TLS.</summary>
private bool _syncingServerFields;
partial void OnSelectedKnownServerChanged(KnownServer? value) partial void OnSelectedKnownServerChanged(KnownServer? value)
{ {
if (value is not null) if (value is null)
ServerInput = $"{value.Host}:{value.PortFor(UseSsl)}"; return;
_syncingServerFields = true;
ServerHost = value.Host;
ServerPort = value.PortFor(UseSsl).ToString();
_syncingServerFields = false;
} }
partial void OnUseSslChanged(bool value) partial void OnUseSslChanged(bool value)
{ {
if (SelectedKnownServer is { } server) if (_syncingServerFields)
ServerInput = $"{server.Host}:{server.PortFor(value)}"; return;
// Attivare/disattivare TLS allinea la porta: porta SSL del server
// selezionato (o default di profilo) quando TLS è on, porta TCP quando off.
_syncingServerFields = true;
ServerPort = SelectedKnownServer is { } server
? server.PortFor(value).ToString()
: (value ? Profile.DefaultSslPort : Profile.DefaultTcpPort).ToString();
_syncingServerFields = false;
}
partial void OnServerPortChanged(string value)
{
if (_syncingServerFields)
return;
if (!int.TryParse(value.Trim(), out var port))
return;
// Scegliere una porta nota allinea il flag TLS, così non si tenta mai
// una connessione in chiaro sulla porta SSL (causa di "connection error").
bool? wantSsl =
SelectedKnownServer is { } s && port == s.SslPort ? true :
SelectedKnownServer is { } t && port == t.TcpPort ? false :
port == Profile.DefaultSslPort ? true :
port == Profile.DefaultTcpPort ? false :
null;
if (wantSsl is bool b && b != UseSsl)
{
_syncingServerFields = true;
UseSsl = b;
_syncingServerFields = false;
}
} }
// ---------- comandi del wizard (§15): un passo alla volta ---------- // ---------- comandi del wizard (§15): un passo alla volta ----------
@@ -678,9 +733,19 @@ public partial class MainWindowViewModel : ViewModelBase
StatusMessage = ""; StatusMessage = "";
try try
{ {
var (host, port) = ParseServer();
// Se l'endpoint richiesto (host/porta/TLS) è diverso da quello della
// connessione attiva, la chiudo: l'utente ha cambiato server o ha
// attivato TLS e si aspetta che la nuova scelta abbia effetto.
if (_client is { } current &&
(current.Host != host || current.Port != port || current.UseSsl != UseSsl))
{
await DisconnectAsync();
}
if (_client is null || !_client.IsConnected) if (_client is null || !_client.IsConnected)
{ {
var (host, port) = ParseServer();
ConnectionStatus = $"{Loc.Tr("conn.connectingto")} {host}:{port}…"; ConnectionStatus = $"{Loc.Tr("conn.connectingto")} {host}:{port}…";
var pins = new CertificatePinStore(AppPaths.CertificatePinsPath(Net)); var pins = new CertificatePinStore(AppPaths.CertificatePinsPath(Net));
_client = await ElectrumClient.ConnectAsync(host, port, UseSsl, pins); _client = await ElectrumClient.ConnectAsync(host, port, UseSsl, pins);
@@ -858,6 +923,22 @@ public partial class MainWindowViewModel : ViewModelBase
} }
} }
/// <summary>
/// Chiude la connessione corrente lasciando il wallet aperto: usata quando
/// l'utente cambia server o attiva TLS e serve riconnettersi al nuovo
/// endpoint. Attende la chiusura del socket prima di tornare.
/// </summary>
private async Task DisconnectAsync()
{
if (_client is { } client)
{
_client = null;
_synchronizer = null;
try { await client.DisposeAsync(); } catch { /* in chiusura */ }
}
IsConnected = false;
}
[RelayCommand] [RelayCommand]
private void CloseWallet() private void CloseWallet()
{ {
@@ -884,9 +965,8 @@ public partial class MainWindowViewModel : ViewModelBase
private (string Host, int Port) ParseServer() private (string Host, int Port) ParseServer()
{ {
var parts = ServerInput.Trim().Split(':'); var host = ServerHost.Trim();
var host = parts[0]; var port = int.TryParse(ServerPort.Trim(), out var p)
var port = parts.Length > 1 && int.TryParse(parts[1], out var p)
? p ? p
: UseSsl ? Profile.DefaultSslPort : Profile.DefaultTcpPort; : UseSsl ? Profile.DefaultSslPort : Profile.DefaultTcpPort;
return (host, port); return (host, port);
+107 -39
View File
@@ -1,6 +1,7 @@
<Window xmlns="https://github.com/avaloniaui" <Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:PalladiumWallet.App.ViewModels" xmlns:vm="using:PalladiumWallet.App.ViewModels"
xmlns:net="using:PalladiumWallet.Core.Net"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="620" mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="620"
@@ -64,6 +65,10 @@
IsChecked="{Binding IsUnitSat, Mode=OneWay}" IsChecked="{Binding IsUnitSat, Mode=OneWay}"
Command="{Binding SetUnitCommand}" CommandParameter="sat"/> Command="{Binding SetUnitCommand}" CommandParameter="sat"/>
</MenuItem> </MenuItem>
<Separator/>
<MenuItem Header="{Binding Loc[settings.server]}"
Command="{Binding OpenServerSettingsCommand}"
IsEnabled="{Binding IsWalletOpen}"/>
</MenuItem> </MenuItem>
</Menu> </Menu>
@@ -173,57 +178,30 @@
</ScrollViewer> </ScrollViewer>
<!-- ============ PANNELLO WALLET ============ --> <!-- ============ PANNELLO WALLET ============ -->
<Grid Grid.Row="1" RowDefinitions="Auto,Auto,*" IsVisible="{Binding IsWalletOpen}" Margin="16"> <Grid Grid.Row="1" RowDefinitions="Auto,*" IsVisible="{Binding IsWalletOpen}" Margin="16">
<!-- Testata: saldo + rete + chiudi --> <!-- Testata: saldo + rete · stato connessione.
<Grid Grid.Row="0" ColumnDefinitions="*,Auto"> Connetti/sincronizza è automatico; chiudi wallet è in File.
Le impostazioni del server sono in Impostazioni → Server. -->
<Grid Grid.Row="0" ColumnDefinitions="*,Auto" Margin="0,0,0,12">
<StackPanel Grid.Column="0" Spacing="2"> <StackPanel Grid.Column="0" Spacing="2">
<TextBlock Text="{Binding BalanceText}" FontSize="30" FontWeight="Bold"/> <TextBlock Text="{Binding BalanceText}" FontSize="30" FontWeight="Bold"/>
<TextBlock Text="{Binding UnconfirmedText}" Foreground="Orange"/> <TextBlock Text="{Binding UnconfirmedText}" Foreground="Orange"/>
<TextBlock Text="{Binding NetworkInfo}" FontSize="12" Foreground="Gray"/> <TextBlock Text="{Binding NetworkInfo}" FontSize="12" Foreground="Gray"/>
</StackPanel> </StackPanel>
<Button Grid.Column="1" Content="{Binding Loc[wallet.close]}" VerticalAlignment="Top" <StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="6"
Command="{Binding CloseWalletCommand}"/> VerticalAlignment="Top">
</Grid> <Ellipse Width="10" Height="10" Fill="LimeGreen" VerticalAlignment="Center"
<!-- Server -->
<Border Grid.Row="1" Margin="0,12,0,12" Padding="10"
BorderBrush="Gray" BorderThickness="1" CornerRadius="6">
<Grid RowDefinitions="Auto,Auto" ColumnDefinitions="Auto,*,Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Loc[wallet.server]}"
VerticalAlignment="Center" Margin="0,0,8,0"/>
<ComboBox Grid.Row="0" Grid.Column="1"
ItemsSource="{Binding KnownServers}"
SelectedItem="{Binding SelectedKnownServer}"
HorizontalAlignment="Stretch"/>
<CheckBox Grid.Row="0" Grid.Column="2" Content="TLS"
IsChecked="{Binding UseSsl}" Margin="8,0"/>
<Button Grid.Row="0" Grid.Column="3" Content="{Binding Loc[wallet.connect]}"
Command="{Binding ConnectAndSyncCommand}" IsEnabled="{Binding !IsSyncing}"/>
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal"
Spacing="8" Margin="0,8,0,0">
<TextBox Text="{Binding ServerInput}" MinWidth="220"
PlaceholderText="{Binding Loc[wallet.manual]}"/>
<Button Content="{Binding Loc[wallet.discover]}"
Command="{Binding DiscoverServersCommand}"/>
<Button Content="{Binding Loc[wallet.resetcert]}"
Command="{Binding ResetCertificatesCommand}"/>
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2"
Orientation="Horizontal" Spacing="6" Margin="8,8,0,0"
VerticalAlignment="Center">
<Ellipse Width="10" Height="10" Fill="LimeGreen"
IsVisible="{Binding IsConnected}"/> IsVisible="{Binding IsConnected}"/>
<Ellipse Width="10" Height="10" Fill="IndianRed" <Ellipse Width="10" Height="10" Fill="IndianRed" VerticalAlignment="Center"
IsVisible="{Binding !IsConnected}"/> IsVisible="{Binding !IsConnected}"/>
<TextBlock Text="{Binding ConnectionStatus}" Foreground="Gray"/> <TextBlock Text="{Binding ConnectionStatus}" Foreground="Gray"
VerticalAlignment="Center"/>
</StackPanel> </StackPanel>
</Grid> </Grid>
</Border>
<!-- Tab: Storico / Invia / Ricevi / Indirizzi / Contatti --> <!-- Tab: Storico / Invia / Ricevi / Indirizzi / Contatti -->
<TabControl Grid.Row="2"> <TabControl Grid.Row="1">
<!-- 1. Storico --> <!-- 1. Storico -->
<TabItem Header="{Binding Loc[tab.history]}"> <TabItem Header="{Binding Loc[tab.history]}">
@@ -430,5 +408,95 @@
</StackPanel> </StackPanel>
</Border> </Border>
</Border> </Border>
<!-- ============ OVERLAY IMPOSTAZIONI SERVER ============ -->
<!-- Stesso pattern dell'overlay indirizzo: apertura/chiusura istantanee.
Accessibile da Impostazioni → Server. -->
<Border Grid.Row="0" Grid.RowSpan="3"
Background="#99000000"
Tapped="OnServerOverlayBackdropTapped"
IsVisible="{Binding IsServerSettingsOpen}">
<Border Background="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}"
BorderBrush="Gray" BorderThickness="1" CornerRadius="8"
Width="560" MaxHeight="540"
HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Margin="24" Spacing="14">
<TextBlock Text="{Binding Loc[server.title]}"
FontSize="18" FontWeight="Bold"/>
<!-- Host + porta separati -->
<Grid ColumnDefinitions="*,Auto,Auto" RowDefinitions="Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Loc[server.host]}"
FontSize="11" Foreground="Gray"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Loc[server.port]}"
FontSize="11" Foreground="Gray" Margin="10,0,0,0" Width="90"/>
<TextBlock Grid.Row="0" Grid.Column="2" Text="TLS"
FontSize="11" Foreground="Gray" Margin="10,0,0,0"/>
<TextBox Grid.Row="1" Grid.Column="0" Text="{Binding ServerHost}"
FontFamily="monospace" Margin="0,2,0,0"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding ServerPort}"
FontFamily="monospace" Width="90" Margin="10,2,0,0"/>
<CheckBox Grid.Row="1" Grid.Column="2" IsChecked="{Binding UseSsl}"
Margin="10,2,0,0" VerticalAlignment="Center"/>
</Grid>
<!-- Azioni -->
<StackPanel Orientation="Horizontal" Spacing="8">
<Button Content="{Binding Loc[wallet.connect]}" Classes="accent"
Command="{Binding ConnectAndSyncCommand}" IsEnabled="{Binding !IsSyncing}"/>
<Button Content="{Binding Loc[wallet.discover]}"
Command="{Binding DiscoverServersCommand}"/>
<Button Content="{Binding Loc[wallet.resetcert]}"
Command="{Binding ResetCertificatesCommand}"/>
</StackPanel>
<!-- Stato connessione -->
<StackPanel Orientation="Horizontal" Spacing="6">
<Ellipse Width="10" Height="10" Fill="LimeGreen" VerticalAlignment="Center"
IsVisible="{Binding IsConnected}"/>
<Ellipse Width="10" Height="10" Fill="IndianRed" VerticalAlignment="Center"
IsVisible="{Binding !IsConnected}"/>
<TextBlock Text="{Binding ConnectionStatus}" Foreground="Gray"
VerticalAlignment="Center"/>
</StackPanel>
<!-- Server conosciuti: clicca per riempire host/porta -->
<TextBlock Text="{Binding Loc[server.known]}" FontSize="11" Foreground="Gray"/>
<ListBox ItemsSource="{Binding KnownServers}"
SelectedItem="{Binding SelectedKnownServer}"
MaxHeight="200">
<ListBox.Styles>
<Style Selector="ListBox:empty">
<Setter Property="Template">
<ControlTemplate>
<TextBlock Text="{Binding Loc[server.empty]}"
Foreground="Gray" FontSize="12"
TextWrapping="Wrap" Margin="8"/>
</ControlTemplate>
</Setter>
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate x:DataType="net:KnownServer">
<Grid ColumnDefinitions="*,Auto">
<TextBlock Grid.Column="0" Text="{Binding Host}"
FontFamily="monospace" FontSize="13"
VerticalAlignment="Center"/>
<TextBlock Grid.Column="1" FontSize="11" Foreground="Gray"
VerticalAlignment="Center">
<Run Text="tcp"/><Run Text="{Binding TcpPort}"/>
<Run Text=" / ssl"/><Run Text="{Binding SslPort}"/>
</TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="{Binding Loc[addr.close]}"
HorizontalAlignment="Right"
Command="{Binding CloseServerSettingsCommand}"/>
</StackPanel>
</Border>
</Border>
</Grid> </Grid>
</Window> </Window>
+10 -4
View File
@@ -65,13 +65,19 @@ public partial class MainWindow : Window
vm.AddressInfo = null; vm.AddressInfo = null;
} }
private void OnServerOverlayBackdropTapped(object? sender, TappedEventArgs e)
{
if (!ReferenceEquals(e.Source, sender)) return;
if (DataContext is MainWindowViewModel vm)
vm.IsServerSettingsOpen = false;
}
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
{ {
if (e.Key == Key.Escape && DataContext is MainWindowViewModel { AddressInfo: not null } vm) if (e.Key == Key.Escape && DataContext is MainWindowViewModel vm)
{ {
vm.AddressInfo = null; if (vm.AddressInfo is not null) { vm.AddressInfo = null; e.Handled = true; return; }
e.Handled = true; if (vm.IsServerSettingsOpen) { vm.IsServerSettingsOpen = false; e.Handled = true; return; }
return;
} }
base.OnKeyDown(e); base.OnKeyDown(e);
} }