Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ namespace UniGetUI.Avalonia.ViewModels;
public partial class PackageDetailsViewModel : ObservableObject
{
public event EventHandler? CloseRequested;
/// <summary>Raised on the UI thread after details have been loaded so the view
/// can (re)populate the inline rich-text blocks.</summary>
public event EventHandler? DetailsLoaded;

public readonly IPackage Package;
public readonly OperationType OperationRole;
Expand Down Expand Up @@ -46,24 +49,22 @@ public partial class PackageDetailsViewModel : ObservableObject
[ObservableProperty]
private string _description = CoreTools.Translate("Loading...");

// ── Basic info ─────────────────────────────────────────────────────────────
// ── Basic info (raw values exposed; the view builds the inline rich text) ──
[ObservableProperty]
private string _versionDisplay = "";

[ObservableProperty]
private string _homepageText = CoreTools.Translate("Loading...");
[ObservableProperty]
private bool _hasHomepageUrl;
private Uri? _homepageUrl;

[ObservableProperty]
private string _author = CoreTools.Translate("Loading...");
[ObservableProperty]
private string _publisher = CoreTools.Translate("Loading...");

[ObservableProperty]
private string _licenseText = CoreTools.Translate("Loading...");
private string? _licenseName;
[ObservableProperty]
private bool _hasLicenseUrl;
private Uri? _licenseUrl;

// ── Actions ────────────────────────────────────────────────────────────────
public string MainActionLabel { get; }
Expand All @@ -78,9 +79,7 @@ public partial class PackageDetailsViewModel : ObservableObject
public string PackageId { get; }

[ObservableProperty]
private string _manifestText = CoreTools.Translate("Loading...");
[ObservableProperty]
private bool _hasManifestUrl;
private Uri? _manifestUrl;

[ObservableProperty]
private string _installerHashLabel = CoreTools.Translate("Installer SHA256") + ":";
Expand All @@ -89,9 +88,7 @@ public partial class PackageDetailsViewModel : ObservableObject
[ObservableProperty]
private string _installerType = CoreTools.Translate("Loading...");
[ObservableProperty]
private string _installerUrlText = CoreTools.Translate("Loading...");
[ObservableProperty]
private bool _hasInstallerUrl;
private Uri? _installerUrl;
[ObservableProperty]
private string _installerSize = "";

Expand All @@ -118,60 +115,43 @@ public partial class PackageDetailsViewModel : ObservableObject

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasScreenshots))]
[NotifyPropertyChangedFor(nameof(SelectedScreenshot))]
[NotifyPropertyChangedFor(nameof(ScreenshotPageLabel))]
[NotifyPropertyChangedFor(nameof(CanGoNextScreenshot))]
private int _screenshotCount;

public bool HasScreenshots => ScreenshotCount > 0;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(SelectedScreenshot))]
[NotifyPropertyChangedFor(nameof(ScreenshotPageLabel))]
[NotifyPropertyChangedFor(nameof(CanGoPrevScreenshot))]
[NotifyPropertyChangedFor(nameof(CanGoNextScreenshot))]
private int _selectedScreenshotIndex;

public Bitmap? SelectedScreenshot =>
ScreenshotCount > 0 && SelectedScreenshotIndex < Screenshots.Count
? Screenshots[SelectedScreenshotIndex]
: null;

public string ScreenshotPageLabel =>
ScreenshotCount > 0 ? $"{SelectedScreenshotIndex + 1} / {ScreenshotCount}" : "";

public bool CanGoPrevScreenshot => SelectedScreenshotIndex > 0;
public bool CanGoNextScreenshot => SelectedScreenshotIndex < ScreenshotCount - 1;

// ── Release notes ──────────────────────────────────────────────────────────
[ObservableProperty]
private string _releaseNotes = CoreTools.Translate("Loading...");
[ObservableProperty]
private string _releaseNotesUrlText = CoreTools.Translate("Loading...");
[ObservableProperty]
private bool _hasReleaseNotesUrl;
private Uri? _releaseNotesUrl;

// ── Translated labels ──────────────────────────────────────────────────────
public string LabelVersion { get; }
public string LabelHomepage { get; } = CoreTools.Translate("Homepage") + ":";
public string LabelAuthor { get; } = CoreTools.Translate("Author") + ":";
public string LabelPublisher { get; } = CoreTools.Translate("Publisher") + ":";
public string LabelLicense { get; } = CoreTools.Translate("License") + ":";
public string LabelPackageId { get; } = CoreTools.Translate("Package ID") + ":";
public string LabelManifest { get; } = CoreTools.Translate("Manifest") + ":";
public string LabelInstallerType { get; } = CoreTools.Translate("Installer Type") + ":";
public string LabelInstallerSize { get; } = CoreTools.Translate("Size") + ":";
public string LabelInstallerUrl { get; } = CoreTools.Translate("Installer URL") + ":";
public string LabelUpdateDate { get; } = CoreTools.Translate("Last updated:");
public string LabelReleaseNotesUrl { get; } = CoreTools.Translate("Release notes URL") + ":";
public string LabelOpen { get; } = CoreTools.Translate("Open");
public string LabelClose { get; } = CoreTools.Translate("Close");
public string HeaderDetails { get; } = CoreTools.Translate("Package details");
public string HeaderDeps { get; } = CoreTools.Translate("Dependencies:");
public string HeaderReleaseNotes { get; } = CoreTools.Translate("Release notes");
public string HeaderScreenshots { get; } = CoreTools.Translate("Screenshots");
public string LabelScreenshotContribute { get; } = CoreTools.Translate(
public string LabelHomepage { get; } = CoreTools.Translate("Homepage");
public string LabelAuthor { get; } = CoreTools.Translate("Author");
public string LabelPublisher { get; } = CoreTools.Translate("Publisher");
public string LabelLicense { get; } = CoreTools.Translate("License");
public string LabelSource { get; } = CoreTools.Translate("Package Manager");
public string LabelPackageId { get; } = CoreTools.Translate("Package ID");
public string LabelManifest { get; } = CoreTools.Translate("Manifest");
public string LabelInstallerType { get; } = CoreTools.Translate("Installer Type");
public string LabelInstallerUrl { get; } = CoreTools.Translate("Installer URL");
public string LabelUpdateDate { get; } = CoreTools.Translate("Last updated");
public string LabelDependencies { get; } = CoreTools.Translate("Dependencies");
public string LabelReleaseNotes { get; } = CoreTools.Translate("Release notes");
public string LabelReleaseNotesUrl { get; } = CoreTools.Translate("Release notes URL");
public string LabelDownloadInstaller { get; } = CoreTools.Translate("Download installer");
public string LabelInstallerNotAvailable { get; } = CoreTools.Translate("Installer not available");
public string LabelNotAvailable { get; } = CoreTools.Translate("Not available");
public string LabelNoDependencies { get; } = CoreTools.Translate("No dependencies specified");
public string LabelInstallationOptions { get; } = CoreTools.Translate("Installation options");
public string LabelSave { get; } = CoreTools.Translate("Save");
public string LabelContributorBanner { get; } = CoreTools.Translate(
"This package has no screenshots or is missing the icon? Contribute to UniGetUI by adding the missing icons and screenshots to our open, public database.");
public string LabelContribute { get; } = CoreTools.Translate("Become a contributor");

public PackageDetailsViewModel(IPackage package, OperationType role)
{
Expand All @@ -197,7 +177,7 @@ public PackageDetailsViewModel(IPackage package, OperationType role)
if (role == OperationType.Install)
{
MainActionLabel = CoreTools.Translate("Install");
LabelVersion = CoreTools.Translate("Version") + ":";
LabelVersion = CoreTools.Translate("Version");
VersionDisplay = available?.VersionString ?? package.VersionString;
AsAdminLabel = CoreTools.Translate("Install as administrator");
InteractiveLabel = CoreTools.Translate("Interactive installation");
Expand All @@ -208,10 +188,10 @@ public PackageDetailsViewModel(IPackage package, OperationType role)
{
MainActionLabel = CoreTools.Translate(
"Update to version {0}", upgradable?.NewVersionString ?? package.NewVersionString);
LabelVersion = CoreTools.Translate("Installed Version") + ":";
LabelVersion = CoreTools.Translate("Installed Version");
VersionDisplay = (upgradable?.VersionString ?? package.VersionString)
+ " \u27a4 "
+ (upgradable?.NewVersionString ?? package.NewVersionString);
+ " "
+ (upgradable?.NewVersionString ?? package.NewVersionString);
AsAdminLabel = CoreTools.Translate("Update as administrator");
InteractiveLabel = CoreTools.Translate("Interactive update");
SkipHashOrRemoveDataLabel = CoreTools.Translate("Skip hash check");
Expand All @@ -220,7 +200,7 @@ public PackageDetailsViewModel(IPackage package, OperationType role)
else
{
MainActionLabel = CoreTools.Translate("Uninstall");
LabelVersion = CoreTools.Translate("Installed Version") + ":";
LabelVersion = CoreTools.Translate("Installed Version");
VersionDisplay = installed?.VersionString ?? package.VersionString;
AsAdminLabel = CoreTools.Translate("Uninstall as administrator");
InteractiveLabel = CoreTools.Translate("Interactive uninstall");
Expand All @@ -229,20 +209,18 @@ public PackageDetailsViewModel(IPackage package, OperationType role)
}
}

[RelayCommand(CanExecute = nameof(CanGoPrevScreenshot))]
[RelayCommand]
private void PreviousScreenshot()
{
SelectedScreenshotIndex = Math.Max(0, SelectedScreenshotIndex - 1);
PreviousScreenshotCommand.NotifyCanExecuteChanged();
NextScreenshotCommand.NotifyCanExecuteChanged();
if (SelectedScreenshotIndex > 0)
SelectedScreenshotIndex--;
}

[RelayCommand(CanExecute = nameof(CanGoNextScreenshot))]
[RelayCommand]
private void NextScreenshot()
{
SelectedScreenshotIndex = Math.Min(ScreenshotCount - 1, SelectedScreenshotIndex + 1);
PreviousScreenshotCommand.NotifyCanExecuteChanged();
NextScreenshotCommand.NotifyCanExecuteChanged();
if (SelectedScreenshotIndex < ScreenshotCount - 1)
SelectedScreenshotIndex++;
}

public async Task LoadDetailsAsync()
Expand All @@ -257,39 +235,28 @@ public async Task LoadDetailsAsync()
IsLoading = false;

Description = details.Description ?? CoreTools.Translate("Not available");
HomepageText = details.HomepageUrl?.ToString() ?? CoreTools.Translate("Not available");
HasHomepageUrl = details.HomepageUrl is not null;
HomepageUrl = details.HomepageUrl;
Author = details.Author ?? CoreTools.Translate("Not available");
Publisher = details.Publisher ?? CoreTools.Translate("Not available");

if (details.License is not null && details.LicenseUrl is not null)
LicenseText = $"{details.License} ({details.LicenseUrl})";
else if (details.License is not null)
LicenseText = details.License;
else if (details.LicenseUrl is not null)
LicenseText = details.LicenseUrl.ToString();
else
LicenseText = CoreTools.Translate("Not available");
HasLicenseUrl = details.LicenseUrl is not null;
LicenseName = details.License;
LicenseUrl = details.LicenseUrl;

ManifestText = details.ManifestUrl?.ToString() ?? CoreTools.Translate("Not available");
HasManifestUrl = details.ManifestUrl is not null;
ManifestUrl = details.ManifestUrl;

if (Package.Manager.Properties.Name.Equals("chocolatey", StringComparison.OrdinalIgnoreCase))
InstallerHashLabel = CoreTools.Translate("Installer SHA512") + ":";

InstallerHash = details.InstallerHash ?? CoreTools.Translate("Not available");
InstallerType = details.InstallerType ?? CoreTools.Translate("Not available");
InstallerUrlText = details.InstallerUrl?.ToString() ?? CoreTools.Translate("Not available");
HasInstallerUrl = details.InstallerUrl is not null;
InstallerUrl = details.InstallerUrl;
InstallerSize = details.InstallerSize > 0
? CoreTools.FormatAsSize(details.InstallerSize, 2)
: CoreTools.Translate("Unknown size");
UpdateDate = details.UpdateDate ?? CoreTools.Translate("Not available");

ReleaseNotes = details.ReleaseNotes ?? CoreTools.Translate("Not available");
ReleaseNotesUrlText = details.ReleaseNotesUrl?.ToString() ?? CoreTools.Translate("Not available");
HasReleaseNotesUrl = details.ReleaseNotesUrl is not null;
ReleaseNotesUrl = details.ReleaseNotesUrl;

if (!CanListDependencies)
{
Expand All @@ -313,23 +280,41 @@ public async Task LoadDetailsAsync()
foreach (var tag in details.Tags)
Tags.Add(tag);
TagCount = Tags.Count;

DetailsLoaded?.Invoke(this, EventArgs.Empty);
}

private async Task LoadIconAsync()
{
try
{
var iconUrl = await Task.Run(Package.GetIconUrl);
if (iconUrl is not null)
var uri = await Task.Run(Package.GetIconUrlIfAny);
if (uri is not null)
{
using var http = new HttpClient(CoreTools.GenericHttpClientParameters);
var bytes = await http.GetByteArrayAsync(iconUrl);
using var ms = new MemoryStream(bytes);
PackageIcon = new Bitmap(ms);
return;
Bitmap? bitmap = null;
if (uri.IsFile)
{
bitmap = await Task.Run(() => new Bitmap(uri.LocalPath));
}
else if (uri.Scheme is "http" or "https")
{
using var http = new HttpClient(CoreTools.GenericHttpClientParameters);
var bytes = await http.GetByteArrayAsync(uri);
using var ms = new MemoryStream(bytes);
bitmap = new Bitmap(ms);
}

if (bitmap is not null)
{
PackageIcon = bitmap;
return;
}
}
}
catch { /* icon is optional */ }
catch (Exception ex)
{
Logger.Warn($"[PackageDetailsViewModel] Failed to load icon: {ex.Message}");
}

try
{
Expand Down Expand Up @@ -359,8 +344,6 @@ await Dispatcher.UIThread.InvokeAsync(() =>
{
Screenshots.Add(bmp);
ScreenshotCount = Screenshots.Count;
PreviousScreenshotCommand.NotifyCanExecuteChanged();
NextScreenshotCommand.NotifyCanExecuteChanged();
});
}
catch { /* skip failed screenshots */ }
Expand All @@ -373,7 +356,7 @@ await Dispatcher.UIThread.InvokeAsync(() =>
}

[RelayCommand]
private static void OpenUrl(string? url)
public static void OpenUrl(string? url)
{
if (string.IsNullOrEmpty(url) || !url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
return;
Expand All @@ -391,7 +374,7 @@ public class DependencyViewModel

public DependencyViewModel(IPackageDetails.Dependency dep)
{
var text = $" \u2022 {dep.Name}";
var text = $" {dep.Name}";
if (!string.IsNullOrEmpty(dep.Version))
text += $" v{dep.Version}";
text += dep.Mandatory
Expand Down
Loading
Loading