diff --git a/src/UniGetUI.Avalonia/App.axaml b/src/UniGetUI.Avalonia/App.axaml
index 78f626305..acfa80dd8 100644
--- a/src/UniGetUI.Avalonia/App.axaml
+++ b/src/UniGetUI.Avalonia/App.axaml
@@ -2,102 +2,14 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="UniGetUI.Avalonia.App"
xmlns:avaloniaApplication1="clr-namespace:UniGetUI.Avalonia"
- xmlns:controls="using:UniGetUI.Avalonia.Views.Controls"
RequestedThemeVariant="Default">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/src/UniGetUI.Avalonia/App.axaml.cs b/src/UniGetUI.Avalonia/App.axaml.cs
index bcaff1376..d3ef1afde 100644
--- a/src/UniGetUI.Avalonia/App.axaml.cs
+++ b/src/UniGetUI.Avalonia/App.axaml.cs
@@ -3,6 +3,7 @@
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
+using Avalonia.Markup.Xaml.Styling;
using Avalonia.Styling;
using UniGetUI.Avalonia.Views;
using UniGetUI.PackageEngine;
@@ -15,6 +16,15 @@ public partial class App : Application
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
+
+ string platform = OperatingSystem.IsWindows() ? "Windows"
+ : OperatingSystem.IsMacOS() ? "macOS"
+ : "Linux";
+
+ Styles.Add(new StyleInclude(new Uri("avares://UniGetUI.Avalonia/"))
+ {
+ Source = new Uri($"avares://UniGetUI.Avalonia/Assets/Styles/Styles.{platform}.axaml")
+ });
}
public override void OnFrameworkInitializationCompleted()
diff --git a/src/UniGetUI.Avalonia/Assets/Styles/Styles.Common.axaml b/src/UniGetUI.Avalonia/Assets/Styles/Styles.Common.axaml
new file mode 100644
index 000000000..0ffcf9ff6
--- /dev/null
+++ b/src/UniGetUI.Avalonia/Assets/Styles/Styles.Common.axaml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/UniGetUI.Avalonia/Assets/Styles/Styles.Linux.axaml b/src/UniGetUI.Avalonia/Assets/Styles/Styles.Linux.axaml
new file mode 100644
index 000000000..06928f792
--- /dev/null
+++ b/src/UniGetUI.Avalonia/Assets/Styles/Styles.Linux.axaml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/UniGetUI.Avalonia/Assets/Styles/Styles.Windows.axaml b/src/UniGetUI.Avalonia/Assets/Styles/Styles.Windows.axaml
new file mode 100644
index 000000000..06928f792
--- /dev/null
+++ b/src/UniGetUI.Avalonia/Assets/Styles/Styles.Windows.axaml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/UniGetUI.Avalonia/Assets/Styles/Styles.macOS.axaml b/src/UniGetUI.Avalonia/Assets/Styles/Styles.macOS.axaml
new file mode 100644
index 000000000..06928f792
--- /dev/null
+++ b/src/UniGetUI.Avalonia/Assets/Styles/Styles.macOS.axaml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/UniGetUI.Avalonia/Infrastructure/AvaloniaOperationRegistry.cs b/src/UniGetUI.Avalonia/Infrastructure/AvaloniaOperationRegistry.cs
index 7860121cb..c9f87837b 100644
--- a/src/UniGetUI.Avalonia/Infrastructure/AvaloniaOperationRegistry.cs
+++ b/src/UniGetUI.Avalonia/Infrastructure/AvaloniaOperationRegistry.cs
@@ -1,5 +1,6 @@
using System.Collections.ObjectModel;
using Avalonia;
+using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Threading;
@@ -23,7 +24,7 @@ public static class AvaloniaOperationRegistry
public static readonly ObservableCollection Operations = new();
/// Bindable view-models shown in the operations panel.
- public static readonly ObservableCollection OperationViewModels = new();
+ public static readonly AvaloniaList OperationViewModels = new();
///
/// Register an operation and create its UI view-model.
diff --git a/src/UniGetUI.Avalonia/MarkupExtensions/TranslateExtension.cs b/src/UniGetUI.Avalonia/MarkupExtensions/TranslateExtension.cs
new file mode 100644
index 000000000..b068c219f
--- /dev/null
+++ b/src/UniGetUI.Avalonia/MarkupExtensions/TranslateExtension.cs
@@ -0,0 +1,21 @@
+using Avalonia.Markup.Xaml;
+using UniGetUI.Core.Tools;
+
+namespace UniGetUI.Avalonia.MarkupExtensions;
+
+///
+/// Markup extension that translates a string at load time.
+/// Usage: Text="{t:Translate Some text}"
+/// For strings with commas use named-property form: Text="{t:Translate Text='A, B, C'}"
+///
+public class TranslateExtension : MarkupExtension
+{
+ public TranslateExtension() { }
+ public TranslateExtension(string text) => Text = text;
+
+ [ConstructorArgument("text")]
+ public string Text { get; set; } = "";
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ => CoreTools.Translate(Text);
+}
diff --git a/src/UniGetUI.Avalonia/Models/PackageCollections.cs b/src/UniGetUI.Avalonia/Models/PackageCollections.cs
index d5c0f7c28..c8442fd35 100644
--- a/src/UniGetUI.Avalonia/Models/PackageCollections.cs
+++ b/src/UniGetUI.Avalonia/Models/PackageCollections.cs
@@ -1,5 +1,6 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
+using Avalonia.Collections;
using UniGetUI.Avalonia.ViewModels.Pages;
using UniGetUI.Interface.Enums;
using UniGetUI.PackageEngine.Interfaces;
@@ -58,9 +59,7 @@ public PackageWrapper(IPackage package, PackagesPageViewModel page)
{
Package = package;
_page = page;
- VersionComboString = package.IsUpgradable
- ? $"{package.VersionString} -> {package.NewVersionString}"
- : package.VersionString;
+ VersionComboString = package.VersionString;
Package.PropertyChanged += Package_PropertyChanged;
UpdateDisplayState();
@@ -104,7 +103,7 @@ public void Dispose()
/// Avalonia-compatible observable collection of PackageWrapper with sorting support
/// (replaces WinUI's ObservablePackageCollection that used SortableObservableCollection).
///
-public sealed class ObservablePackageCollection : ObservableCollection
+public sealed class ObservablePackageCollection : AvaloniaList
{
public enum Sorter
{
diff --git a/src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj b/src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj
index 9e39a8861..29249d077 100644
--- a/src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj
+++ b/src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj
@@ -63,6 +63,9 @@
+
+
+
@@ -132,6 +135,9 @@
ManagersHomepage.axaml
Code
+
+ OperationOutputWindow.axaml
+
diff --git a/src/UniGetUI.Avalonia/ViewModels/DialogPages/ManageIgnoredUpdatesViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/DialogPages/ManageIgnoredUpdatesViewModel.cs
index 60f36e274..5a740ddaf 100644
--- a/src/UniGetUI.Avalonia/ViewModels/DialogPages/ManageIgnoredUpdatesViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/DialogPages/ManageIgnoredUpdatesViewModel.cs
@@ -14,16 +14,16 @@ public partial class ManageIgnoredUpdatesViewModel : ObservableObject
public event EventHandler? CloseRequested;
public string Title { get; } = CoreTools.Translate("Manage ignored updates");
- public string Description { get; } = CoreTools.Translate("The packages listed here won't be taken in account when checking for updates. Click the button on their right to stop ignoring their updates.");
+ public string Description { get; } = CoreTools.Translate("The packages listed here won't be taken in account when checking for updates. Double-click them or click the button on their right to stop ignoring their updates.");
public string ResetLabel { get; } = CoreTools.Translate("Reset list");
public string ResetConfirm { get; } = CoreTools.Translate("Do you really want to reset the ignored updates list? This action cannot be reverted");
public string ResetYes { get; } = CoreTools.Translate("Yes");
public string ResetNo { get; } = CoreTools.Translate("No");
public string EmptyLabel { get; } = CoreTools.Translate("No ignored updates");
- public string ColName { get; } = CoreTools.Translate("Package name");
+ public string ColName { get; } = CoreTools.Translate("Package Name");
public string ColId { get; } = CoreTools.Translate("Package ID");
public string ColVersion { get; } = CoreTools.Translate("Ignored version");
- public string ColNewVersion { get; } = CoreTools.Translate("Available update");
+ public string ColNewVersion { get; } = CoreTools.Translate("New version");
public string ColManager { get; } = CoreTools.Translate("Source");
public ObservableCollection Entries { get; } = [];
diff --git a/src/UniGetUI.Avalonia/ViewModels/DialogPages/OperationOutputViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/DialogPages/OperationOutputViewModel.cs
new file mode 100644
index 000000000..11e07aa2a
--- /dev/null
+++ b/src/UniGetUI.Avalonia/ViewModels/DialogPages/OperationOutputViewModel.cs
@@ -0,0 +1,16 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using UniGetUI.PackageOperations;
+
+namespace UniGetUI.Avalonia.ViewModels.DialogPages;
+
+public partial class OperationOutputViewModel : ObservableObject
+{
+ [ObservableProperty] private string _title = "";
+ [ObservableProperty] private string _outputText = "";
+
+ public OperationOutputViewModel(AbstractOperation operation)
+ {
+ Title = operation.Metadata.Title;
+ OutputText = string.Join("\n", operation.GetOutput().Select(x => x.Item1));
+ }
+}
diff --git a/src/UniGetUI.Avalonia/ViewModels/DialogPages/OperationViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/DialogPages/OperationViewModel.cs
index fdbba1fdf..1011cd473 100644
--- a/src/UniGetUI.Avalonia/ViewModels/DialogPages/OperationViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/DialogPages/OperationViewModel.cs
@@ -1,21 +1,20 @@
using System.Collections.ObjectModel;
-using System.ComponentModel;
-using System.Runtime.CompilerServices;
using System.Windows.Input;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Media;
using Avalonia.Threading;
+using CommunityToolkit.Mvvm.ComponentModel;
using UniGetUI.Avalonia.Infrastructure;
-using UniGetUI.Avalonia.Views;
+using UniGetUI.Avalonia.Views.DialogPages;
using UniGetUI.Core.Tools;
using UniGetUI.PackageEngine.Enums;
using UniGetUI.PackageOperations;
namespace UniGetUI.Avalonia.ViewModels;
-public sealed class OperationViewModel : INotifyPropertyChanged
+public sealed partial class OperationViewModel : ViewModelBase
{
public AbstractOperation Operation { get; }
@@ -25,6 +24,15 @@ public sealed class OperationViewModel : INotifyPropertyChanged
public ICommand ButtonCommand { get; }
public ICommand ShowDetailsCommand { get; }
+ // ── Bindable properties ───────────────────────────────────────────────────
+ [ObservableProperty] private string _title;
+ [ObservableProperty] private string _liveLine;
+ [ObservableProperty] private string _buttonText;
+ [ObservableProperty] private bool _progressIndeterminate;
+ [ObservableProperty] private double _progressValue;
+ [ObservableProperty] private IBrush _progressBrush;
+ [ObservableProperty] private IBrush _backgroundBrush;
+
public OperationViewModel(AbstractOperation operation)
{
Operation = operation;
@@ -124,61 +132,6 @@ private void ShowDetails()
}
}
- // ── Bindable properties ───────────────────────────────────────────────────
- private string _title;
- public string Title
- {
- get => _title;
- set { _title = value; OnPropertyChanged(); }
- }
-
- private string _liveLine;
- public string LiveLine
- {
- get => _liveLine;
- set { _liveLine = value; OnPropertyChanged(); }
- }
-
- private string _buttonText;
- public string ButtonText
- {
- get => _buttonText;
- set { _buttonText = value; OnPropertyChanged(); }
- }
-
- private bool _progressIndeterminate;
- public bool ProgressIndeterminate
- {
- get => _progressIndeterminate;
- set { _progressIndeterminate = value; OnPropertyChanged(); }
- }
-
- private double _progressValue;
- public double ProgressValue
- {
- get => _progressValue;
- set { _progressValue = value; OnPropertyChanged(); }
- }
-
- private IBrush _progressBrush;
- public IBrush ProgressBrush
- {
- get => _progressBrush;
- set { _progressBrush = value; OnPropertyChanged(); }
- }
-
- private IBrush _backgroundBrush;
- public IBrush BackgroundBrush
- {
- get => _backgroundBrush;
- set { _backgroundBrush = value; OnPropertyChanged(); }
- }
-
- public event PropertyChangedEventHandler? PropertyChanged;
-
- private void OnPropertyChanged([CallerMemberName] string? name = null)
- => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
-
// ── Minimal ICommand implementation ───────────────────────────────────────
private sealed class SyncCommand(Action action) : ICommand
{
diff --git a/src/UniGetUI.Avalonia/ViewModels/DialogPages/PackageDetailsViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/DialogPages/PackageDetailsViewModel.cs
index 3eeb9679e..20bc1b7fd 100644
--- a/src/UniGetUI.Avalonia/ViewModels/DialogPages/PackageDetailsViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/DialogPages/PackageDetailsViewModel.cs
@@ -127,15 +127,15 @@ public partial class PackageDetailsViewModel : ObservableObject
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("Installer size") + ":";
+ 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 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("Details");
- public string HeaderDeps { get; } = CoreTools.Translate("Dependencies");
+ public string HeaderDetails { get; } = CoreTools.Translate("Package details");
+ public string HeaderDeps { get; } = CoreTools.Translate("Dependencies:");
public string HeaderReleaseNotes { get; } = CoreTools.Translate("Release notes");
public PackageDetailsViewModel(IPackage package, OperationType role)
@@ -297,6 +297,7 @@ private static void OpenUrl(string? url)
catch { }
}
+ [RelayCommand]
public void RequestClose() => CloseRequested?.Invoke(this, EventArgs.Empty);
}
diff --git a/src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs
index 8c66fcde4..4937e3eef 100644
--- a/src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs
@@ -1,10 +1,11 @@
-using System.Collections.ObjectModel;
using System.Collections.Specialized;
using Avalonia;
+using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
using UniGetUI.Avalonia.Infrastructure;
using UniGetUI.Avalonia.ViewModels.Pages;
using UniGetUI.Avalonia.Views;
@@ -46,8 +47,7 @@ public partial class MainWindowViewModel : ViewModelBase
public event EventHandler? CurrentPageChanged;
// ─── Operations panel ─────────────────────────────────────────────────────
- public ObservableCollection Operations
- => AvaloniaOperationRegistry.OperationViewModels;
+ public AvaloniaList Operations => AvaloniaOperationRegistry.OperationViewModels;
[ObservableProperty]
private bool _operationsPanelVisible;
@@ -120,6 +120,9 @@ private void OnPageViewModelPropertyChanged(object? sender, System.ComponentMode
private bool _telemetryWarnerVisible;
// ─── Constructor ─────────────────────────────────────────────────────────
+ [RelayCommand]
+ private void ToggleSidebar() => Sidebar.IsPaneOpen = !Sidebar.IsPaneOpen;
+
public MainWindowViewModel()
{
DiscoverPage = new DiscoverSoftwarePage();
@@ -337,7 +340,14 @@ private async Task ShowAboutDialog()
Sidebar.SelectNavButtonForPage(_currentPage);
}
+ // ─── Banner close commands ────────────────────────────────────────────────
+ [RelayCommand] private void CloseUpdatesBanner() => UpdatesBannerVisible = false;
+ [RelayCommand] private void CloseErrorBanner() => ErrorBannerVisible = false;
+ [RelayCommand] private void CloseWinGetWarningBanner() => WinGetWarningBannerVisible = false;
+ [RelayCommand] private void CloseTelemetryWarner() => TelemetryWarnerVisible = false;
+
// ─── Search box ──────────────────────────────────────────────────────────
+ [RelayCommand]
public void SubmitGlobalSearch()
{
if (CurrentPageContent is ISearchBoxPage page)
diff --git a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/AdministratorViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/AdministratorViewModel.cs
index 1bdafbb40..fc1c5ea48 100644
--- a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/AdministratorViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/AdministratorViewModel.cs
@@ -1,12 +1,68 @@
using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
using UniGetUI.Avalonia.ViewModels;
+using UniGetUI.Core.SettingsEngine;
+using UniGetUI.Core.SettingsEngine.SecureSettings;
+using UniGetUI.Core.Tools;
namespace UniGetUI.Avalonia.ViewModels.Pages.SettingsPages;
public partial class AdministratorViewModel : ViewModelBase
{
- ///
- /// True when elevation is NOT prohibited — controls enabled-state of the cache-admin-rights cards.
- ///
- [ObservableProperty] private bool _isElevationEnabled = true;
+ public event EventHandler? RestartRequired;
+
+ // ── Warning banner strings ────────────────────────────────────────────
+ public string WarningTitle { get; } = CoreTools.Translate("Warning") + "!";
+
+ public string WarningBody1 { get; } =
+ CoreTools.Translate("The following settings may pose a security risk, hence they are disabled by default.")
+ + " "
+ + CoreTools.Translate("Enable the settings below if and only if you fully understand what they do, and the implications they may have.");
+
+ public string WarningBody2 { get; } =
+ CoreTools.Translate("The settings will list, in their descriptions, the potential security issues they may have.");
+
+ // ── Observable state ─────────────────────────────────────────────────
+ /// True when elevation is NOT prohibited — controls enabled-state of the cache-admin-rights cards.
+ [ObservableProperty] private bool _isElevationEnabled;
+
+ /// Mirrors AllowCLIArguments toggle — controls IsEnabled of AllowImportingCLIArguments.
+ [ObservableProperty] private bool _isCLIArgumentsEnabled;
+
+ /// Mirrors AllowPrePostOpCommand toggle — controls IsEnabled of AllowImportingPrePostInstallCommands.
+ [ObservableProperty] private bool _isPrePostCommandsEnabled;
+
+ public AdministratorViewModel()
+ {
+ _isElevationEnabled = !Settings.Get(Settings.K.ProhibitElevation);
+ _isCLIArgumentsEnabled = SecureSettings.Get(SecureSettings.K.AllowCLIArguments);
+ _isPrePostCommandsEnabled = SecureSettings.Get(SecureSettings.K.AllowPrePostOpCommand);
+ }
+
+ // ── Commands ─────────────────────────────────────────────────────────
+
+ [RelayCommand]
+ private void RestartCache() => _ = CoreTools.ResetUACForCurrentProcess();
+
+ [RelayCommand]
+ private void ShowRestartRequired() => RestartRequired?.Invoke(this, EventArgs.Empty);
+
+ [RelayCommand]
+ private void RefreshElevationState()
+ {
+ IsElevationEnabled = !Settings.Get(Settings.K.ProhibitElevation);
+ RestartRequired?.Invoke(this, EventArgs.Empty);
+ }
+
+ [RelayCommand]
+ private void RefreshCLIState()
+ {
+ IsCLIArgumentsEnabled = SecureSettings.Get(SecureSettings.K.AllowCLIArguments);
+ }
+
+ [RelayCommand]
+ private void RefreshPrePostState()
+ {
+ IsPrePostCommandsEnabled = SecureSettings.Get(SecureSettings.K.AllowPrePostOpCommand);
+ }
}
diff --git a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/BackupViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/BackupViewModel.cs
index 30a87fc35..75a5de617 100644
--- a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/BackupViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/BackupViewModel.cs
@@ -11,10 +11,23 @@ namespace UniGetUI.Avalonia.ViewModels.Pages.SettingsPages;
public partial class BackupViewModel : ViewModelBase
{
+ public event EventHandler? RestartRequired;
+
[ObservableProperty] private bool _isLocalBackupEnabled;
[ObservableProperty] private string _backupDirectoryLabel = "";
- public BackupViewModel() => RefreshDirectoryLabel();
+ public BackupViewModel()
+ {
+ _isLocalBackupEnabled = CoreSettings.Get(CoreSettings.K.EnablePackageBackup_LOCAL);
+ RefreshDirectoryLabel();
+ }
+
+ [RelayCommand]
+ private void EnableLocalBackupChanged()
+ {
+ IsLocalBackupEnabled = CoreSettings.Get(CoreSettings.K.EnablePackageBackup_LOCAL);
+ RestartRequired?.Invoke(this, EventArgs.Empty);
+ }
private void RefreshDirectoryLabel()
{
diff --git a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/ExperimentalViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/ExperimentalViewModel.cs
index b7bb36ce7..869b36b79 100644
--- a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/ExperimentalViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/ExperimentalViewModel.cs
@@ -1,7 +1,12 @@
+using CommunityToolkit.Mvvm.Input;
using UniGetUI.Avalonia.ViewModels;
namespace UniGetUI.Avalonia.ViewModels.Pages.SettingsPages;
public partial class ExperimentalViewModel : ViewModelBase
{
+ public event EventHandler? RestartRequired;
+
+ [RelayCommand]
+ private void ShowRestartRequired() => RestartRequired?.Invoke(this, EventArgs.Empty);
}
diff --git a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/GeneralViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/GeneralViewModel.cs
index c84f116b0..a51bcef0a 100644
--- a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/GeneralViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/GeneralViewModel.cs
@@ -5,6 +5,7 @@
using global::Avalonia.Platform.Storage;
using UniGetUI.Avalonia.ViewModels;
using UniGetUI.Avalonia.Views.DialogPages;
+using UniGetUI.Avalonia.Views.Pages.SettingsPages;
using UniGetUI.Core.Logging;
using UniGetUI.Core.Tools;
using CoreSettings = global::UniGetUI.Core.SettingsEngine.Settings;
@@ -64,5 +65,13 @@ private void ResetSettings(Visual? _)
}
public event EventHandler? RestartRequired;
+ public event EventHandler? NavigationRequested;
+
private void OnRestartRequired() => RestartRequired?.Invoke(this, EventArgs.Empty);
+
+ [RelayCommand]
+ private void ShowRestartRequired() => RestartRequired?.Invoke(this, EventArgs.Empty);
+
+ [RelayCommand]
+ private void NavigateToInterface() => NavigationRequested?.Invoke(this, typeof(Interface_P));
}
diff --git a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/Interface_PViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/Interface_PViewModel.cs
index b3d66352f..b4f2a3f24 100644
--- a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/Interface_PViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/Interface_PViewModel.cs
@@ -15,6 +15,9 @@ public partial class Interface_PViewModel : ViewModelBase
public event EventHandler? RestartRequired;
+ [RelayCommand]
+ private void ShowRestartRequired() => RestartRequired?.Invoke(this, EventArgs.Empty);
+
[RelayCommand]
private static void EditAutostartSettings()
=> CoreTools.Launch("ms-settings:startupapps");
diff --git a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/InternetViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/InternetViewModel.cs
index fdab2f0b4..606c00b81 100644
--- a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/InternetViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/InternetViewModel.cs
@@ -1,14 +1,175 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Layout;
+using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
using UniGetUI.Avalonia.ViewModels;
+using UniGetUI.Avalonia.Views.Controls.Settings;
+using UniGetUI.Core.Tools;
+using UniGetUI.PackageEngine;
+using UniGetUI.PackageEngine.ManagerClasses.Manager;
using CoreSettings = global::UniGetUI.Core.SettingsEngine.Settings;
namespace UniGetUI.Avalonia.ViewModels.Pages.SettingsPages;
public partial class InternetViewModel : ViewModelBase
{
+ public event EventHandler? RestartRequired;
+
+ private TextBox? _usernameBox;
+ private TextBox? _passwordBox;
+ private ProgressBar? _savingIndicator;
+
[ObservableProperty] private bool _isProxyEnabled;
[ObservableProperty] private bool _isProxyAuthEnabled;
+ public InternetViewModel()
+ {
+ _isProxyEnabled = CoreSettings.Get(CoreSettings.K.EnableProxy);
+ _isProxyAuthEnabled = CoreSettings.Get(CoreSettings.K.EnableProxyAuth);
+ }
+
+ public SettingsCard BuildCredentialsCard()
+ {
+ _savingIndicator = new ProgressBar
+ {
+ IsIndeterminate = true,
+ Opacity = 0,
+ Margin = new Thickness(0, -8, 0, 0),
+ };
+
+ _usernameBox = new TextBox
+ {
+ Watermark = CoreTools.Translate("Username"),
+ MinWidth = 200,
+ Margin = new Thickness(0, 0, 0, 4),
+ };
+
+ _passwordBox = new TextBox
+ {
+ Watermark = CoreTools.Translate("Password"),
+ MinWidth = 200,
+ PasswordChar = '●',
+ };
+
+ var creds = CoreSettings.GetProxyCredentials();
+ if (creds is not null)
+ {
+ _usernameBox.Text = creds.UserName;
+ _passwordBox.Text = creds.Password;
+ }
+
+ _usernameBox.TextChanged += (_, _) => _ = SaveCredentialsAsync();
+ _passwordBox.TextChanged += (_, _) => _ = SaveCredentialsAsync();
+
+ var stack = new StackPanel { Orientation = Orientation.Vertical };
+ stack.Children.Add(_savingIndicator);
+ stack.Children.Add(_usernameBox);
+ stack.Children.Add(_passwordBox);
+
+ return new SettingsCard
+ {
+ CornerRadius = new CornerRadius(0, 0, 8, 8),
+ BorderThickness = new Thickness(1, 0, 1, 1),
+ Header = CoreTools.Translate("Credentials"),
+ Description = CoreTools.Translate("It is not guaranteed that the provided credentials will be stored safely"),
+ Content = stack,
+ };
+ }
+
+ public Control BuildProxyCompatTable()
+ {
+ var noStr = CoreTools.Translate("No");
+ var yesStr = CoreTools.Translate("Yes");
+ var partStr = CoreTools.Translate("Partially");
+
+ var headerRow = new Grid { ColumnDefinitions = new ColumnDefinitions("*,Auto,Auto,Auto,*"), Margin = new Thickness(0, 0, 0, 8) };
+ headerRow.Children.Add(WithCol(new TextBlock { Text = CoreTools.Translate("Package manager"), FontWeight = FontWeight.Bold, TextWrapping = TextWrapping.Wrap }, 1));
+ headerRow.Children.Add(WithCol(new TextBlock { Text = CoreTools.Translate("Compatible with proxy"), FontWeight = FontWeight.Bold, TextWrapping = TextWrapping.Wrap, Margin = new Thickness(16, 0, 0, 0) }, 2));
+ headerRow.Children.Add(WithCol(new TextBlock { Text = CoreTools.Translate("Compatible with authentication"), FontWeight = FontWeight.Bold, TextWrapping = TextWrapping.Wrap, Margin = new Thickness(16, 0, 0, 0) }, 3));
+
+ var managerCol = new StackPanel { Orientation = Orientation.Vertical, Spacing = 6 };
+ var proxyCol = new StackPanel { Orientation = Orientation.Vertical, Spacing = 6 };
+ var authCol = new StackPanel { Orientation = Orientation.Vertical, Spacing = 6 };
+
+ foreach (var manager in PEInterface.Managers)
+ {
+ managerCol.Children.Add(new TextBlock { Text = manager.DisplayName, TextAlignment = TextAlignment.Center });
+
+ var proxyLevel = manager.Capabilities.SupportsProxy;
+ proxyCol.Children.Add(StatusBadge(
+ proxyLevel is ProxySupport.No ? noStr : (proxyLevel is ProxySupport.Partially ? partStr : yesStr),
+ proxyLevel is ProxySupport.Yes ? Colors.Green : (proxyLevel is ProxySupport.Partially ? Colors.Orange : Colors.Red)));
+
+ authCol.Children.Add(StatusBadge(
+ manager.Capabilities.SupportsProxyAuth ? yesStr : noStr,
+ manager.Capabilities.SupportsProxyAuth ? Colors.Green : Colors.Red));
+ }
+
+ var dataRow = new Grid { ColumnDefinitions = new ColumnDefinitions("*,Auto,Auto,Auto,*"), ColumnSpacing = 16 };
+ dataRow.Children.Add(WithCol(managerCol, 1));
+ dataRow.Children.Add(WithCol(proxyCol, 2));
+ dataRow.Children.Add(WithCol(authCol, 3));
+
+ var tableStack = new StackPanel { Orientation = Orientation.Vertical };
+ tableStack.Children.Add(headerRow);
+ tableStack.Children.Add(dataRow);
+
+ return new SettingsCard
+ {
+ CornerRadius = new CornerRadius(8),
+ Header = CoreTools.Translate("Proxy compatibility table"),
+ Description = tableStack,
+ };
+ }
+
+ private static Border StatusBadge(string text, Color color) => new Border
+ {
+ CornerRadius = new CornerRadius(4),
+ Padding = new Thickness(4, 2),
+ BorderThickness = new Thickness(1),
+ Background = new SolidColorBrush(Color.FromArgb(60, color.R, color.G, color.B)),
+ BorderBrush = new SolidColorBrush(Color.FromArgb(120, color.R, color.G, color.B)),
+ Child = new TextBlock { Text = text, TextAlignment = TextAlignment.Center },
+ };
+
+ private static Control WithCol(Control c, int col) { Grid.SetColumn(c, col); return c; }
+
+ private async Task SaveCredentialsAsync()
+ {
+ if (_usernameBox is null || _passwordBox is null || _savingIndicator is null) return;
+ _savingIndicator.Opacity = 1;
+ string u = _usernameBox.Text ?? "";
+ string p = _passwordBox.Text ?? "";
+ await Task.Delay(500);
+ if ((_usernameBox.Text ?? "") != u) return;
+ if ((_passwordBox.Text ?? "") != p) return;
+ CoreSettings.SetProxyCredentials(u, p);
+ InternetViewModel.ApplyProxyToProcess();
+ _savingIndicator.Opacity = 0;
+ }
+
+ [RelayCommand]
+ private void RefreshProxyEnabled()
+ {
+ IsProxyEnabled = CoreSettings.Get(CoreSettings.K.EnableProxy);
+ ApplyProxyToProcess();
+ }
+
+ [RelayCommand]
+ private void RefreshProxyAuthEnabled()
+ {
+ IsProxyAuthEnabled = CoreSettings.Get(CoreSettings.K.EnableProxyAuth);
+ ApplyProxyToProcess();
+ }
+
+ [RelayCommand]
+ private static void ApplyProxy() => ApplyProxyToProcess();
+
+ [RelayCommand]
+ private void ShowRestartRequired() => RestartRequired?.Invoke(this, EventArgs.Empty);
+
public static void ApplyProxyToProcess()
{
var proxyUri = CoreSettings.GetProxyUrl();
diff --git a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/NotificationsViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/NotificationsViewModel.cs
index 2e56f1003..5db3b75be 100644
--- a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/NotificationsViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/NotificationsViewModel.cs
@@ -1,10 +1,24 @@
using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
using UniGetUI.Avalonia.ViewModels;
+using CoreSettings = global::UniGetUI.Core.SettingsEngine.Settings;
namespace UniGetUI.Avalonia.ViewModels.Pages.SettingsPages;
public partial class NotificationsViewModel : ViewModelBase
{
- [ObservableProperty] private bool _isSystemTrayEnabled = true;
+ [ObservableProperty] private bool _isSystemTrayEnabled;
[ObservableProperty] private bool _isNotificationsEnabled;
+
+ public NotificationsViewModel()
+ {
+ _isSystemTrayEnabled = !CoreSettings.Get(CoreSettings.K.DisableSystemTray);
+ _isNotificationsEnabled = !CoreSettings.Get(CoreSettings.K.DisableNotifications);
+ }
+
+ [RelayCommand]
+ private void UpdateNotificationsEnabled()
+ {
+ IsNotificationsEnabled = !CoreSettings.Get(CoreSettings.K.DisableNotifications);
+ }
}
diff --git a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/OperationsViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/OperationsViewModel.cs
index 21162b923..cf0893d2c 100644
--- a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/OperationsViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/OperationsViewModel.cs
@@ -1,7 +1,33 @@
+using CommunityToolkit.Mvvm.Input;
using UniGetUI.Avalonia.ViewModels;
+using UniGetUI.Avalonia.Views.Pages.SettingsPages;
+using UniGetUI.PackageOperations;
+using CoreSettings = global::UniGetUI.Core.SettingsEngine.Settings;
namespace UniGetUI.Avalonia.ViewModels.Pages.SettingsPages;
public partial class OperationsViewModel : ViewModelBase
{
+ public event EventHandler? RestartRequired;
+ public event EventHandler? NavigationRequested;
+
+ /// Items for the parallel operation count ComboboxCard.
+ public IReadOnlyList ParallelOpCounts { get; } =
+ [.. Enumerable.Range(1, 10).Select(i => i.ToString()), "15", "20", "30", "50", "75", "100"];
+
+ [RelayCommand]
+ private void UpdateMaxOperations()
+ {
+ if (int.TryParse(CoreSettings.GetValue(CoreSettings.K.ParallelOperationCount), out int value))
+ AbstractOperation.MAX_OPERATIONS = value;
+ }
+
+ [RelayCommand]
+ private void ShowRestartRequired() => RestartRequired?.Invoke(this, EventArgs.Empty);
+
+ [RelayCommand]
+ private void NavigateToUpdates() => NavigationRequested?.Invoke(this, typeof(Updates));
+
+ [RelayCommand]
+ private void NavigateToAdministrator() => NavigationRequested?.Invoke(this, typeof(Administrator));
}
diff --git a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/SettingsBasePageViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/SettingsBasePageViewModel.cs
index e47bc0e40..eb8cc66c9 100644
--- a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/SettingsBasePageViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/SettingsBasePageViewModel.cs
@@ -1,4 +1,6 @@
+using Avalonia.Controls.ApplicationLifetimes;
using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
using UniGetUI.Avalonia.ViewModels;
using UniGetUI.Core.Tools;
@@ -9,6 +11,23 @@ public partial class SettingsBasePageViewModel : ViewModelBase
[ObservableProperty] private string _title = "";
[ObservableProperty] private bool _isRestartBannerVisible;
+ public event EventHandler? BackRequested;
+
public string RestartBannerText => CoreTools.Translate("Restart UniGetUI to fully apply changes");
public string RestartButtonText => CoreTools.Translate("Restart UniGetUI");
+
+ [RelayCommand]
+ private void Back() => BackRequested?.Invoke(this, EventArgs.Empty);
+
+ [RelayCommand]
+ private static void RestartApp()
+ {
+ var exe = Environment.ProcessPath;
+ if (exe is not null)
+ System.Diagnostics.Process.Start(
+ new System.Diagnostics.ProcessStartInfo(exe) { UseShellExecute = true });
+ (global::Avalonia.Application.Current?.ApplicationLifetime
+ as IClassicDesktopStyleApplicationLifetime)
+ ?.Shutdown();
+ }
}
diff --git a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/SettingsHomepageViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/SettingsHomepageViewModel.cs
index b7ac97527..06d34dfbc 100644
--- a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/SettingsHomepageViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/SettingsHomepageViewModel.cs
@@ -1,7 +1,21 @@
+using CommunityToolkit.Mvvm.Input;
using UniGetUI.Avalonia.ViewModels;
+using UniGetUI.Avalonia.Views.Pages.SettingsPages;
namespace UniGetUI.Avalonia.ViewModels.Pages.SettingsPages;
public partial class SettingsHomepageViewModel : ViewModelBase
{
+ public event EventHandler? NavigationRequested;
+
+ [RelayCommand] private void NavigateToGeneral() => NavigationRequested?.Invoke(this, typeof(General));
+ [RelayCommand] private void NavigateToInterface() => NavigationRequested?.Invoke(this, typeof(Interface_P));
+ [RelayCommand] private void NavigateToNotifications() => NavigationRequested?.Invoke(this, typeof(Notifications));
+ [RelayCommand] private void NavigateToUpdates() => NavigationRequested?.Invoke(this, typeof(Updates));
+ [RelayCommand] private void NavigateToOperations() => NavigationRequested?.Invoke(this, typeof(Operations));
+ [RelayCommand] private void NavigateToInternet() => NavigationRequested?.Invoke(this, typeof(Internet));
+ [RelayCommand] private void NavigateToBackup() => NavigationRequested?.Invoke(this, typeof(Backup));
+ [RelayCommand] private void NavigateToAdministrator() => NavigationRequested?.Invoke(this, typeof(Administrator));
+ [RelayCommand] private void NavigateToExperimental() => NavigationRequested?.Invoke(this, typeof(Experimental));
+ [RelayCommand] private void NavigateToManagers() => NavigationRequested?.Invoke(this, typeof(ManagersHomepage));
}
diff --git a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/UpdatesViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/UpdatesViewModel.cs
index f9f90ab56..1111d8c03 100644
--- a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/UpdatesViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/UpdatesViewModel.cs
@@ -1,9 +1,52 @@
using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
using UniGetUI.Avalonia.ViewModels;
+using UniGetUI.Avalonia.Views.Pages.SettingsPages;
+using UniGetUI.Core.Tools;
+using CoreSettings = global::UniGetUI.Core.SettingsEngine.Settings;
namespace UniGetUI.Avalonia.ViewModels.Pages.SettingsPages;
public partial class UpdatesViewModel : ViewModelBase
{
+ public event EventHandler? RestartRequired;
+ public event EventHandler? NavigationRequested;
+
[ObservableProperty] private bool _isAutoCheckEnabled;
+
+ /// Items for the update interval ComboboxCard, in display/value pairs.
+ public IReadOnlyList<(string Name, string Value)> IntervalItems { get; } =
+ [
+ (CoreTools.Translate("{0} minutes", 10), "600"),
+ (CoreTools.Translate("{0} minutes", 30), "1800"),
+ (CoreTools.Translate("1 hour"), "3600"),
+ (CoreTools.Translate("{0} hours", 2), "7200"),
+ (CoreTools.Translate("{0} hours", 4), "14400"),
+ (CoreTools.Translate("{0} hours", 8), "28800"),
+ (CoreTools.Translate("{0} hours", 12), "43200"),
+ (CoreTools.Translate("1 day"), "86400"),
+ (CoreTools.Translate("{0} days", 2), "172800"),
+ (CoreTools.Translate("{0} days", 3), "259200"),
+ (CoreTools.Translate("1 week"), "604800"),
+ ];
+
+ public UpdatesViewModel()
+ {
+ _isAutoCheckEnabled = !CoreSettings.Get(CoreSettings.K.DisableAutoCheckforUpdates);
+ }
+
+ [RelayCommand]
+ private void UpdateAutoCheckEnabled()
+ {
+ IsAutoCheckEnabled = !CoreSettings.Get(CoreSettings.K.DisableAutoCheckforUpdates);
+ }
+
+ [RelayCommand]
+ private void ShowRestartRequired() => RestartRequired?.Invoke(this, EventArgs.Empty);
+
+ [RelayCommand]
+ private void NavigateToOperations() => NavigationRequested?.Invoke(this, typeof(Operations));
+
+ [RelayCommand]
+ private void NavigateToAdministrator() => NavigationRequested?.Invoke(this, typeof(Administrator));
}
diff --git a/src/UniGetUI.Avalonia/ViewModels/SidebarViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/SidebarViewModel.cs
index 57c0b37d4..b51b9769e 100644
--- a/src/UniGetUI.Avalonia/ViewModels/SidebarViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/SidebarViewModel.cs
@@ -1,5 +1,9 @@
using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
using UniGetUI.Avalonia.Views;
+using UniGetUI.Core.Data;
+using UniGetUI.Core.SettingsEngine;
+using UniGetUI.Core.Tools;
namespace UniGetUI.Avalonia.ViewModels;
@@ -19,6 +23,18 @@ public partial class SidebarViewModel : ViewModelBase
partial void OnUpdatesBadgeCountChanged(int value) =>
UpdatesBadgeVisible = value > 0;
+ partial void OnUpdatesBadgeVisibleChanged(bool _)
+ {
+ OnPropertyChanged(nameof(UpdatesBadgeExpandedVisible));
+ OnPropertyChanged(nameof(UpdatesBadgeCompactVisible));
+ }
+
+ partial void OnBundlesBadgeVisibleChanged(bool _)
+ {
+ OnPropertyChanged(nameof(BundlesBadgeExpandedVisible));
+ OnPropertyChanged(nameof(BundlesBadgeCompactVisible));
+ }
+
// ─── Loading indicators ───────────────────────────────────────────────────
[ObservableProperty]
private bool _discoverIsLoading;
@@ -29,6 +45,27 @@ partial void OnUpdatesBadgeCountChanged(int value) =>
[ObservableProperty]
private bool _installedIsLoading;
+ // ─── Pane open/closed ─────────────────────────────────────────────────────
+ [ObservableProperty]
+ private bool isPaneOpen = !Settings.Get(Settings.K.CollapseNavMenuOnWideScreen);
+
+ partial void OnIsPaneOpenChanged(bool value)
+ {
+ Settings.Set(Settings.K.CollapseNavMenuOnWideScreen, !value);
+ OnPropertyChanged(nameof(PaneWidth));
+ OnPropertyChanged(nameof(UpdatesBadgeExpandedVisible));
+ OnPropertyChanged(nameof(UpdatesBadgeCompactVisible));
+ OnPropertyChanged(nameof(BundlesBadgeExpandedVisible));
+ OnPropertyChanged(nameof(BundlesBadgeCompactVisible));
+ }
+
+ public double PaneWidth => IsPaneOpen ? 250 : 72;
+
+ public bool UpdatesBadgeExpandedVisible => UpdatesBadgeVisible && IsPaneOpen;
+ public bool UpdatesBadgeCompactVisible => UpdatesBadgeVisible && !IsPaneOpen;
+ public bool BundlesBadgeExpandedVisible => BundlesBadgeVisible && IsPaneOpen;
+ public bool BundlesBadgeCompactVisible => BundlesBadgeVisible && !IsPaneOpen;
+
// ─── Selected page ────────────────────────────────────────────────────────
[ObservableProperty]
private PageType _selectedPageType = PageType.Null;
@@ -36,8 +73,14 @@ partial void OnUpdatesBadgeCountChanged(int value) =>
// ─── Navigation ──────────────────────────────────────────────────────────
public event EventHandler? NavigationRequested;
- public void RequestNavigation(PageType page) =>
- NavigationRequested?.Invoke(this, page);
+ public string VersionLabel { get; } = CoreTools.Translate("WingetUI Version {0}", CoreData.VersionName);
+
+ [RelayCommand]
+ public void RequestNavigation(string? pageName)
+ {
+ if (Enum.TryParse(pageName, out var page))
+ NavigationRequested?.Invoke(this, page);
+ }
public void SelectNavButtonForPage(PageType page) =>
SelectedPageType = page;
diff --git a/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs
index 4824c7c8a..69c8ea8dc 100644
--- a/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs
@@ -2,11 +2,17 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
+using Avalonia;
+using Avalonia.Collections;
+using Avalonia.Controls;
+using Avalonia.Layout;
+using Avalonia.Media;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using UniGetUI.Avalonia.Infrastructure;
using UniGetUI.Avalonia.ViewModels;
+using UniGetUI.Avalonia.Views.Controls;
using UniGetUI.Core.SettingsEngine;
using UniGetUI.Core.Tools;
using UniGetUI.PackageEngine.Enums;
@@ -19,6 +25,8 @@ namespace UniGetUI.Avalonia.ViewModels.Pages;
public enum SearchMode { Both, Name, Id, Exact, Similar }
+public enum PackageViewMode { List = 0, Grid = 1, Icons = 2 }
+
public enum ReloadReason
{
FirstRun,
@@ -116,7 +124,7 @@ public partial class PackagesPageViewModel : ViewModelBase
[ObservableProperty] private bool _newVersionHeaderVisible;
[ObservableProperty] private bool _reloadButtonVisible;
[ObservableProperty] private bool _isFilterPaneOpen;
- [ObservableProperty] private int _viewMode;
+ [ObservableProperty] private PackageViewMode _viewMode;
[ObservableProperty] private int _sortFieldIndex;
[ObservableProperty] private bool _sortAscending = true;
[ObservableProperty] private bool _instantSearch = true;
@@ -132,8 +140,8 @@ public partial class PackagesPageViewModel : ViewModelBase
// ─── Collections ──────────────────────────────────────────────────────────
public ObservablePackageCollection FilteredPackages { get; } = new();
- public ObservableCollection SourceNodes { get; } = new();
- public ObservableCollection