diff --git a/src/App/Localization/Loc.cs b/src/App/Localization/Loc.cs
index 9f42caf..c6c367b 100644
--- a/src/App/Localization/Loc.cs
+++ b/src/App/Localization/Loc.cs
@@ -63,6 +63,10 @@ public sealed class Loc
["help.tab.info"] = ["Info", "Info", "Info", "Info", "Info", "Info"],
["help.tab.donate"] = ["Dona", "Donate", "Donar", "Faire un don", "Doar", "Spenden"],
["help.bug.report"] = ["Segnala un bug", "Report a bug", "Informar un error", "Signaler un bug", "Reportar um bug", "Fehler melden"],
+ ["update.title"] = ["Aggiornamento disponibile", "Update available", "Actualización disponible", "Mise à jour disponible", "Atualização disponível", "Update verfügbar"],
+ ["update.message"] = ["È disponibile una nuova versione:", "A new version is available:", "Hay una nueva versión disponible:", "Une nouvelle version est disponible :", "Uma nova versão está disponível:", "Eine neue Version ist verfügbar:"],
+ ["update.download"] = ["Scarica", "Download", "Descargar", "Télécharger", "Baixar", "Herunterladen"],
+ ["update.dismiss"] = ["Ignora", "Dismiss", "Ignorar", "Ignorer", "Ignorar", "Verwerfen"],
["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.",
diff --git a/src/App/ViewModels/MainWindowViewModel.Update.cs b/src/App/ViewModels/MainWindowViewModel.Update.cs
new file mode 100644
index 0000000..3a63ae1
--- /dev/null
+++ b/src/App/ViewModels/MainWindowViewModel.Update.cs
@@ -0,0 +1,36 @@
+using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using PalladiumWallet.Core.Net;
+
+namespace PalladiumWallet.App.ViewModels;
+
+public partial class MainWindowViewModel
+{
+ // ---- update-available overlay ----
+
+ [ObservableProperty]
+ private bool isUpdateAvailableOpen;
+
+ [ObservableProperty]
+ private string updateAvailableTag = "";
+
+ public string UpdateReleaseUrl { get; private set; } = "";
+
+ [RelayCommand]
+ private void CloseUpdateAvailable() => IsUpdateAvailableOpen = false;
+
+ ///
+ /// Fire-and-forget check run once at startup. Best-effort: any failure or an
+ /// up-to-date app silently does nothing (see ).
+ ///
+ private async Task CheckForUpdatesAsync()
+ {
+ var latest = await UpdateChecker.CheckAsync(AppVersion);
+ if (latest is null) return;
+
+ UpdateReleaseUrl = latest.HtmlUrl;
+ UpdateAvailableTag = latest.Tag;
+ IsUpdateAvailableOpen = true;
+ }
+}
diff --git a/src/App/ViewModels/MainWindowViewModel.cs b/src/App/ViewModels/MainWindowViewModel.cs
index 94fff7a..fd6b655 100644
--- a/src/App/ViewModels/MainWindowViewModel.cs
+++ b/src/App/ViewModels/MainWindowViewModel.cs
@@ -158,6 +158,7 @@ public partial class MainWindowViewModel : ViewModelBase
_keepAliveTimer = new DispatcherTimer { Interval = System.TimeSpan.FromSeconds(20) };
_keepAliveTimer.Tick += async (_, _) => await KeepAliveTickAsync();
_keepAliveTimer.Start();
+ _ = CheckForUpdatesAsync();
}
private async System.Threading.Tasks.Task KeepAliveTickAsync()
diff --git a/src/App/Views/MainView.axaml b/src/App/Views/MainView.axaml
index b528ac4..72f8518 100644
--- a/src/App/Views/MainView.axaml
+++ b/src/App/Views/MainView.axaml
@@ -1638,5 +1638,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/App/Views/MainView.axaml.cs b/src/App/Views/MainView.axaml.cs
index 34955ca..322089c 100644
--- a/src/App/Views/MainView.axaml.cs
+++ b/src/App/Views/MainView.axaml.cs
@@ -129,6 +129,13 @@ public partial class MainView : UserControl
vm.IsHelpOpen = false;
}
+ private void OnUpdateAvailableOverlayBackdropTapped(object? sender, TappedEventArgs e)
+ {
+ if (!ReferenceEquals(e.Source, sender)) return;
+ if (DataContext is MainWindowViewModel vm)
+ vm.IsUpdateAvailableOpen = false;
+ }
+
private async void OnOpenBugReportClick(object? sender, RoutedEventArgs e)
{
const string issueUrl = "https://github.com/davide3011/PalladiumWallet/issues/new?template=bug_report.yml";
@@ -137,6 +144,15 @@ public partial class MainView : UserControl
await launcher.LaunchUriAsync(new Uri(issueUrl));
}
+ private async void OnOpenReleasePageClick(object? sender, RoutedEventArgs e)
+ {
+ if (DataContext is not MainWindowViewModel vm || string.IsNullOrEmpty(vm.UpdateReleaseUrl)) return;
+ var launcher = TopLevel.GetTopLevel(this)?.Launcher;
+ if (launcher is not null)
+ await launcher.LaunchUriAsync(new Uri(vm.UpdateReleaseUrl));
+ vm.IsUpdateAvailableOpen = false;
+ }
+
// Esc (desktop) or Back (Android) closes the topmost open overlay.
protected override void OnKeyDown(KeyEventArgs e)
{
@@ -149,6 +165,7 @@ public partial class MainView : UserControl
if (vm.IsWalletInfoOpen) { vm.CloseWalletInfoCommand.Execute(null); e.Handled = true; return; }
if (vm.IsSettingsOpen) { vm.IsSettingsOpen = false; e.Handled = true; return; }
if (vm.IsHelpOpen) { vm.IsHelpOpen = false; e.Handled = true; return; }
+ if (vm.IsUpdateAvailableOpen) { vm.IsUpdateAvailableOpen = false; e.Handled = true; return; }
}
base.OnKeyDown(e);
}
diff --git a/src/Core/Net/UpdateChecker.cs b/src/Core/Net/UpdateChecker.cs
new file mode 100644
index 0000000..2d1482f
--- /dev/null
+++ b/src/Core/Net/UpdateChecker.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Net.Http;
+using System.Net.Http.Json;
+using System.Text.Json.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace PalladiumWallet.Core.Net;
+
+/// A GitHub release newer than the running app.
+public sealed record LatestRelease(string Tag, string HtmlUrl);
+
+///
+/// Checks the latest GitHub release for the project against the running app version.
+/// Best-effort only: any network/parse failure or an up-to-date app both resolve to null,
+/// so callers never need to distinguish "no update" from "couldn't check".
+///
+public static class UpdateChecker
+{
+ private const string ReleasesApiUrl = "https://api.github.com/repos/davide3011/PalladiumWallet/releases/latest";
+
+ private sealed class GitHubReleaseResponse
+ {
+ [JsonPropertyName("tag_name")]
+ public string? TagName { get; set; }
+
+ [JsonPropertyName("html_url")]
+ public string? HtmlUrl { get; set; }
+ }
+
+ public static async Task CheckAsync(string currentVersion, CancellationToken ct = default)
+ {
+ try
+ {
+ using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
+ http.DefaultRequestHeaders.UserAgent.ParseAdd("PalladiumWallet");
+ using var response = await http.GetAsync(ReleasesApiUrl, ct).ConfigureAwait(false);
+ if (!response.IsSuccessStatusCode) return null;
+
+ var release = await response.Content
+ .ReadFromJsonAsync(ct)
+ .ConfigureAwait(false);
+ if (release?.TagName is not { Length: > 0 } tag) return null;
+
+ if (!TryParse(tag, out var remote) || !TryParse(currentVersion, out var local))
+ return null;
+ if (remote <= local) return null;
+
+ return new LatestRelease(tag, release.HtmlUrl ?? $"https://github.com/davide3011/PalladiumWallet/releases/tag/{tag}");
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ /// Parses "v1.2.3" / "1.2.3-beta" style tags into a comparable .
+ private static bool TryParse(string raw, out Version version)
+ {
+ var s = raw.Trim();
+ if (s.StartsWith('v') || s.StartsWith('V')) s = s[1..];
+ var dash = s.IndexOf('-');
+ if (dash >= 0) s = s[..dash];
+ return Version.TryParse(s, out version!);
+ }
+}