diff --git a/src/UniGetUI.Avalonia/App.axaml.cs b/src/UniGetUI.Avalonia/App.axaml.cs
index 123c81ab92..4f6059fa10 100644
--- a/src/UniGetUI.Avalonia/App.axaml.cs
+++ b/src/UniGetUI.Avalonia/App.axaml.cs
@@ -5,6 +5,7 @@
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Platform;
using Avalonia.Styling;
+using Avalonia.Threading;
using UniGetUI.Avalonia.Infrastructure;
using UniGetUI.Avalonia.Views;
using UniGetUI.Avalonia.Views.DialogPages;
@@ -35,6 +36,21 @@ public override void Initialize()
public override void OnFrameworkInitializationCompleted()
{
+ if (OperatingSystem.IsWindows())
+ {
+ // Safety net for NativeWebView (WebView2) initialization failures thrown
+ // asynchronously on the dispatcher. Without this the app crashes; with it
+ // the Help page shows a fallback "Open in browser" button.
+ Dispatcher.UIThread.UnhandledException += (_, e) =>
+ {
+ if (e.Exception is InvalidOperationException { Message: var msg }
+ && msg.Contains("child window for native control host"))
+ {
+ e.Handled = true;
+ }
+ };
+ }
+
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
if (OperatingSystem.IsMacOS())
diff --git a/src/UniGetUI.Avalonia/Infrastructure/AvaloniaOperationRegistry.cs b/src/UniGetUI.Avalonia/Infrastructure/AvaloniaOperationRegistry.cs
index 36a3440ef6..a1fe74b164 100644
--- a/src/UniGetUI.Avalonia/Infrastructure/AvaloniaOperationRegistry.cs
+++ b/src/UniGetUI.Avalonia/Infrastructure/AvaloniaOperationRegistry.cs
@@ -93,6 +93,43 @@ public static void Add(AbstractOperation op)
};
}
+ public static void RetryFailed()
+ {
+ var failed = OperationViewModels
+ .Where(vm => vm.Operation.Status is OperationStatus.Failed)
+ .ToList();
+ foreach (var vm in failed)
+ vm.Operation.Retry(AbstractOperation.RetryMode.Retry);
+ }
+
+ public static void ClearSuccessful()
+ {
+ var succeeded = OperationViewModels
+ .Where(vm => vm.Operation.Status is OperationStatus.Succeeded)
+ .ToList();
+ foreach (var vm in succeeded)
+ Remove(vm);
+ }
+
+ public static void ClearFinished()
+ {
+ var finished = OperationViewModels
+ .Where(vm => vm.Operation.Status
+ is OperationStatus.Succeeded or OperationStatus.Failed or OperationStatus.Canceled)
+ .ToList();
+ foreach (var vm in finished)
+ Remove(vm);
+ }
+
+ public static void CancelAll()
+ {
+ var active = OperationViewModels
+ .Where(vm => vm.Operation.Status is OperationStatus.Running or OperationStatus.InQueue)
+ .ToList();
+ foreach (var vm in active)
+ vm.Operation.Cancel();
+ }
+
/// Remove a view-model (and its backing operation) from the panel. Called by the Close button.
public static void Remove(OperationViewModel vm)
{
diff --git a/src/UniGetUI.Avalonia/Models/PackageCollections.cs b/src/UniGetUI.Avalonia/Models/PackageCollections.cs
index c8442fd350..676e7b3dc3 100644
--- a/src/UniGetUI.Avalonia/Models/PackageCollections.cs
+++ b/src/UniGetUI.Avalonia/Models/PackageCollections.cs
@@ -1,7 +1,13 @@
+using System.Collections.Concurrent;
using System.Collections.ObjectModel;
using System.ComponentModel;
+using System.Net.Http;
using Avalonia.Collections;
+using Avalonia.Media.Imaging;
+using Avalonia.Threading;
using UniGetUI.Avalonia.ViewModels.Pages;
+using UniGetUI.Core.SettingsEngine;
+using UniGetUI.Core.Tools;
using UniGetUI.Interface.Enums;
using UniGetUI.PackageEngine.Interfaces;
@@ -13,6 +19,12 @@ namespace UniGetUI.PackageEngine.PackageClasses;
///
public sealed class PackageWrapper : INotifyPropertyChanged, IDisposable
{
+ private static readonly HttpClient _iconHttpClient = new(CoreTools.GenericHttpClientParameters)
+ {
+ Timeout = TimeSpan.FromSeconds(8),
+ };
+ private static readonly ConcurrentDictionary _iconCache = new();
+
public IPackage Package { get; }
public PackageWrapper Self => this;
public int Index { get; set; }
@@ -21,6 +33,19 @@ public sealed class PackageWrapper : INotifyPropertyChanged, IDisposable
private readonly PackagesPageViewModel _page;
+ private Bitmap? _iconBitmap;
+ public Bitmap? IconBitmap
+ {
+ get => _iconBitmap;
+ private set
+ {
+ _iconBitmap = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IconBitmap)));
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(HasCustomIcon)));
+ }
+ }
+ public bool HasCustomIcon => _iconBitmap is not null;
+
public bool IsChecked
{
get => Package.IsChecked;
@@ -35,6 +60,8 @@ public bool IsChecked
public string VersionComboString { get; }
public string ListedNameTooltip { get; private set; } = "";
public float ListedOpacity { get; private set; } = 1.0f;
+ public string TagIconPath { get; private set; } = "";
+ public bool TagIconVisible { get; private set; }
public string SourceIconPath => IconTypeToSvgPath(Package.Source.IconId);
@@ -63,6 +90,43 @@ public PackageWrapper(IPackage package, PackagesPageViewModel page)
Package.PropertyChanged += Package_PropertyChanged;
UpdateDisplayState();
+
+ if (!Settings.Get(Settings.K.DisableIconsOnPackageLists))
+ _ = LoadIconAsync();
+ }
+
+ private async Task LoadIconAsync()
+ {
+ long hash = Package.GetHash();
+ if (_iconCache.TryGetValue(hash, out Bitmap? cached))
+ {
+ if (cached is not null)
+ IconBitmap = cached;
+ return;
+ }
+
+ try
+ {
+ var uri = Package.GetIconUrlIfAny();
+ if (uri is null) { _iconCache[hash] = null; return; }
+
+ Bitmap bitmap;
+ if (uri.IsFile)
+ {
+ bitmap = new Bitmap(uri.LocalPath);
+ }
+ else if (uri.Scheme is "http" or "https")
+ {
+ var bytes = await _iconHttpClient.GetByteArrayAsync(uri);
+ using var ms = new MemoryStream(bytes);
+ bitmap = new Bitmap(ms);
+ }
+ else { _iconCache[hash] = null; return; }
+
+ _iconCache[hash] = bitmap;
+ await Dispatcher.UIThread.InvokeAsync(() => IconBitmap = bitmap);
+ }
+ catch { _iconCache[hash] = null; }
}
private void Package_PropertyChanged(object? sender, PropertyChangedEventArgs e)
@@ -72,6 +136,8 @@ private void Package_PropertyChanged(object? sender, PropertyChangedEventArgs e)
UpdateDisplayState();
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ListedOpacity)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ListedNameTooltip)));
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TagIconPath)));
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TagIconVisible)));
}
else if (e.PropertyName == nameof(Package.IsChecked))
{
@@ -91,6 +157,21 @@ private void UpdateDisplayState()
_ => 1.0f,
};
ListedNameTooltip = Package.Name;
+
+ string tagName = Package.Tag switch
+ {
+ PackageTag.AlreadyInstalled => "installed_filled",
+ PackageTag.IsUpgradable => "upgradable_filled",
+ PackageTag.Pinned => "pin_filled",
+ PackageTag.OnQueue => "sandclock",
+ PackageTag.BeingProcessed => "loading_filled",
+ PackageTag.Failed => "warning_filled",
+ _ => "",
+ };
+ TagIconVisible = tagName.Length > 0;
+ TagIconPath = TagIconVisible
+ ? $"avares://UniGetUI.Avalonia/Assets/Symbols/{tagName}.svg"
+ : "";
}
public void Dispose()
diff --git a/src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj b/src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj
index b9e586a99a..062c71e555 100644
--- a/src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj
+++ b/src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj
@@ -20,6 +20,7 @@
UniGetUI.Avalonia
..\UniGetUI\icon.ico
true
+ app.manifest
diff --git a/src/UniGetUI.Avalonia/ViewModels/DialogPages/InstallOptionsViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/DialogPages/InstallOptionsViewModel.cs
index 9b90bca88f..6abbca80a0 100644
--- a/src/UniGetUI.Avalonia/ViewModels/DialogPages/InstallOptionsViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/DialogPages/InstallOptionsViewModel.cs
@@ -305,6 +305,10 @@ public InstallOptionsViewModel(IPackage package, OperationType operation, Instal
}
// ── Commands ──────────────────────────────────────────────────────────────
+
+ /// Captures the current UI state into the options object without closing.
+ public void ApplyChanges() => ApplyToOptions();
+
[RelayCommand]
private void Save()
{
diff --git a/src/UniGetUI.Avalonia/ViewModels/DialogPages/OperationOutputViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/DialogPages/OperationOutputViewModel.cs
index 11e07aa2ac..84b78798a0 100644
--- a/src/UniGetUI.Avalonia/ViewModels/DialogPages/OperationOutputViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/DialogPages/OperationOutputViewModel.cs
@@ -1,4 +1,8 @@
+using System.Collections.ObjectModel;
+using Avalonia.Media;
+using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
+using UniGetUI.Avalonia.ViewModels.Pages.LogPages;
using UniGetUI.PackageOperations;
namespace UniGetUI.Avalonia.ViewModels.DialogPages;
@@ -6,11 +10,31 @@ namespace UniGetUI.Avalonia.ViewModels.DialogPages;
public partial class OperationOutputViewModel : ObservableObject
{
[ObservableProperty] private string _title = "";
- [ObservableProperty] private string _outputText = "";
+ public ObservableCollection OutputLines { get; } = new();
+
+ private static readonly IBrush _errorBrush = new SolidColorBrush(Color.Parse("#FF6B6B"));
+ private static readonly IBrush _debugBrush = new SolidColorBrush(Color.Parse("#888888"));
+ private static readonly IBrush _normalBrush = Brushes.White;
public OperationOutputViewModel(AbstractOperation operation)
{
Title = operation.Metadata.Title;
- OutputText = string.Join("\n", operation.GetOutput().Select(x => x.Item1));
+
+ foreach (var (text, type) in operation.GetOutput())
+ OutputLines.Add(MakeLine(text, type));
+
+ operation.LogLineAdded += (_, ev) =>
+ Dispatcher.UIThread.Post(() => OutputLines.Add(MakeLine(ev.Item1, ev.Item2)));
+ }
+
+ private LogLineItem MakeLine(string text, AbstractOperation.LineType type)
+ {
+ IBrush brush = type switch
+ {
+ AbstractOperation.LineType.Error => _errorBrush,
+ AbstractOperation.LineType.VerboseDetails => _debugBrush,
+ _ => _normalBrush,
+ };
+ return new LogLineItem(text, brush);
}
}
diff --git a/src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs
index 6b06f35896..45a6fce0bc 100644
--- a/src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs
@@ -64,6 +64,24 @@ public partial class MainWindowViewModel : ViewModelBase
[ObservableProperty]
private bool _operationsPanelVisible;
+ [ObservableProperty]
+ private bool _operationsPanelExpanded = true;
+
+ [RelayCommand]
+ private void ToggleOperationsPanel() => OperationsPanelExpanded = !OperationsPanelExpanded;
+
+ [RelayCommand]
+ private void RetryFailedOperations() => AvaloniaOperationRegistry.RetryFailed();
+
+ [RelayCommand]
+ private void ClearSuccessfulOperations() => AvaloniaOperationRegistry.ClearSuccessful();
+
+ [RelayCommand]
+ private void ClearFinishedOperations() => AvaloniaOperationRegistry.ClearFinished();
+
+ [RelayCommand]
+ private void CancelAllOperations() => AvaloniaOperationRegistry.CancelAll();
+
// ─── Sidebar ─────────────────────────────────────────────────────────────
public SidebarViewModel Sidebar { get; } = new();
diff --git a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/BackupViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/BackupViewModel.cs
index 2ec3ffac46..c4933e7d6f 100644
--- a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/BackupViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/BackupViewModel.cs
@@ -224,6 +224,16 @@ private async Task DoCloudBackup()
{
_isLoading = true;
UpdateCloudControlsEnabled();
+ try { await DoCloudBackupStatic(); }
+ finally
+ {
+ _isLoading = false;
+ UpdateCloudControlsEnabled();
+ }
+ }
+
+ public static async Task DoCloudBackupStatic()
+ {
try
{
var packages = InstalledPackagesLoader.Instance?.Packages.ToList() ?? [];
@@ -236,11 +246,6 @@ private async Task DoCloudBackup()
Logger.Error("An error occurred while performing a CLOUD backup:");
Logger.Error(ex);
}
- finally
- {
- _isLoading = false;
- UpdateCloudControlsEnabled();
- }
}
[RelayCommand]
diff --git a/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs
index 280bca9c1c..217182eb83 100644
--- a/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs
@@ -70,7 +70,7 @@ public class SourceTreeNode : INotifyPropertyChanged
public string? PackageID { get; init; }
public string? Version { get; init; }
public string? Source { get; init; }
- public List Children { get; } = [];
+ public AvaloniaList Children { get; } = new();
public event PropertyChangedEventHandler? PropertyChanged;
@@ -92,7 +92,11 @@ public bool IsExpanded
public partial class PackagesPageViewModel : ViewModelBase
{
public double FilterPaneColumnWidth => IsFilterPaneOpen ? 220.0 : 0.0;
- partial void OnIsFilterPaneOpenChanged(bool _) => OnPropertyChanged(nameof(FilterPaneColumnWidth));
+ partial void OnIsFilterPaneOpenChanged(bool value)
+ {
+ OnPropertyChanged(nameof(FilterPaneColumnWidth));
+ Settings.SetDictionaryItem(Settings.K.HideToggleFilters, PageName, !value);
+ }
// ─── Static config (set once in constructor) ──────────────────────────────
public readonly string PageName;
@@ -205,6 +209,10 @@ public PackagesPageViewModel(PackagesPageData data)
? (PackageViewMode)savedMode
: PackageViewMode.List;
+ // Restore per-page filter pane open/closed state (default: open).
+ // Use backing field to avoid writing to settings during construction.
+ _isFilterPaneOpen = !Settings.GetDictionaryItem(Settings.K.HideToggleFilters, PageName);
+
_localPackagesNode.PackageName = CoreTools.Translate("Local");
if (Loader.IsLoading)
@@ -584,11 +592,12 @@ public void AddPackageToSourcesList(IPackage package)
UsedSourcesForManager[source.Manager].Add(source);
var item = new SourceTreeNode
{
- PackageName = source.Manager.DisplayName,
+ PackageName = source.Name,
PackageID = package.Id,
Version = package.VersionString,
Source = package.Source.Name
};
+ item.PropertyChanged += OnRootSourceNodePropertyChanged;
NodesForSources.TryAdd(source, item);
if (source.IsVirtualManager)
@@ -610,7 +619,11 @@ public void AddPackageToSourcesList(IPackage package)
public void ClearSourcesList()
{
foreach (var node in SourceNodes)
+ {
node.PropertyChanged -= OnRootSourceNodePropertyChanged;
+ foreach (var child in node.Children)
+ child.PropertyChanged -= OnRootSourceNodePropertyChanged;
+ }
UsedManagers.Clear();
SourceNodes.Clear();
UsedSourcesForManager.Clear();
@@ -631,11 +644,37 @@ private void OnRootSourceNodePropertyChanged(object? sender, PropertyChangedEven
FilterPackages();
}
private List GetAllSourceNodes() => SourceNodes.ToList();
- private List GetSelectedSourceNodes() => SourceNodes.Where(n => n.IsSelected).ToList();
+
+ private List GetSelectedSourceNodes()
+ {
+ var result = new List();
+ foreach (var root in SourceNodes)
+ {
+ if (root.IsSelected) result.Add(root);
+ result.AddRange(root.Children.Where(c => c.IsSelected));
+ }
+ return result;
+ }
public void SetSourceNodeSelected(SourceTreeNode node, bool selected) => node.IsSelected = selected;
- public void ClearSourceSelection() { foreach (var n in SourceNodes) n.IsSelected = false; }
- public void SelectAllSources() { foreach (var n in SourceNodes) n.IsSelected = true; }
+
+ public void ClearSourceSelection()
+ {
+ foreach (var n in SourceNodes)
+ {
+ n.IsSelected = false;
+ foreach (var c in n.Children) c.IsSelected = false;
+ }
+ }
+
+ public void SelectAllSources()
+ {
+ foreach (var n in SourceNodes)
+ {
+ n.IsSelected = true;
+ foreach (var c in n.Children) c.IsSelected = true;
+ }
+ }
// ─── Header texts ─────────────────────────────────────────────────────────
public void UpdateHeaderTexts()
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsControl.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsControl.axaml
new file mode 100644
index 0000000000..f6406c1d65
--- /dev/null
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsControl.axaml
@@ -0,0 +1,362 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsControl.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsControl.axaml.cs
new file mode 100644
index 0000000000..c5678453e9
--- /dev/null
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsControl.axaml.cs
@@ -0,0 +1,35 @@
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Platform.Storage;
+using UniGetUI.Avalonia.ViewModels;
+
+namespace UniGetUI.Avalonia.Views.DialogPages;
+
+public partial class InstallOptionsControl : UserControl
+{
+ private InstallOptionsViewModel ViewModel => (InstallOptionsViewModel)DataContext!;
+
+ public InstallOptionsControl()
+ {
+ InitializeComponent();
+ }
+
+ public void FocusProfileSelector() => ProfileSelectorComboBox.Focus();
+
+ private async void SelectDir_Click(object? sender, RoutedEventArgs e)
+ {
+ var topLevel = TopLevel.GetTopLevel(this);
+ if (topLevel is null) return;
+ var results = await topLevel.StorageProvider.OpenFolderPickerAsync(
+ new FolderPickerOpenOptions { AllowMultiple = false });
+ if (results is [{ } folder])
+ ViewModel.LocationText = folder.TryGetLocalPath() ?? folder.Name;
+ }
+
+ private void KillProcessBox_KeyDown(object? sender, KeyEventArgs e)
+ {
+ if (e.Key is Key.Return or Key.Enter or Key.OemComma)
+ ViewModel.AddKillProcessCommand.Execute(null);
+ }
+}
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml
index eb941fbc26..c7f0747af0 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml
@@ -1,10 +1,10 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml.cs
index 082290efb3..c7f2407ac6 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml.cs
@@ -1,7 +1,4 @@
using Avalonia.Controls;
-using Avalonia.Input;
-using Avalonia.Interactivity;
-using Avalonia.Platform.Storage;
using Avalonia.Threading;
using UniGetUI.Avalonia.ViewModels;
using UniGetUI.PackageEngine.Enums;
@@ -26,21 +23,6 @@ public InstallOptionsWindow(IPackage package, OperationType operation, InstallOp
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
- Dispatcher.UIThread.Post(() => ProfileSelectorComboBox.Focus(), DispatcherPriority.Background);
- }
-
- private async void SelectDir_Click(object? sender, RoutedEventArgs e)
- {
- var results = await StorageProvider.OpenFolderPickerAsync(
- new FolderPickerOpenOptions { AllowMultiple = false });
- if (results is [{ } folder])
- ((InstallOptionsViewModel)DataContext!).LocationText =
- folder.TryGetLocalPath() ?? folder.Name;
- }
-
- private void KillProcessBox_KeyDown(object? sender, KeyEventArgs e)
- {
- if (e.Key is Key.Return or Key.Enter or Key.OemComma)
- ((InstallOptionsViewModel)DataContext!).AddKillProcessCommand.Execute(null);
+ Dispatcher.UIThread.Post(OptionsControl.FocusProfileSelector, DispatcherPriority.Background);
}
}
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/OperationOutputWindow.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/OperationOutputWindow.axaml
index 5f0752d627..4965582d6e 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/OperationOutputWindow.axaml
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/OperationOutputWindow.axaml
@@ -1,7 +1,8 @@
-
+ VerticalScrollBarVisibility="Auto"
+ Background="{DynamicResource AppDialogDarkBackground}"
+ CornerRadius="6">
+
+
+
+
+
+
+
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/OperationOutputWindow.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/OperationOutputWindow.axaml.cs
index 5f519e39cc..419c99c48a 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/OperationOutputWindow.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/OperationOutputWindow.axaml.cs
@@ -11,12 +11,14 @@ public OperationOutputWindow(AbstractOperation operation)
{
DataContext = new OperationOutputViewModel(operation);
InitializeComponent();
+
+ ((OperationOutputViewModel)DataContext).OutputLines.CollectionChanged +=
+ (_, _) => Dispatcher.UIThread.Post(OutputScroll.ScrollToEnd, DispatcherPriority.Background);
}
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
OutputScroll.ScrollToEnd();
- Dispatcher.UIThread.Post(() => OutputTextBox.Focus(), DispatcherPriority.Background);
}
}
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml
index 8783fcd55f..f0490c25be 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml
@@ -1,8 +1,9 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
OnMainAction();
ActionVariantsButton.Flyout = BuildActionFlyout();
+ InstallOptionsSaveButton.Click += (_, _) => _ = SaveInstallOptionsAsync();
}
- protected override void OnOpened(EventArgs e)
+ protected override async void OnOpened(EventArgs e)
{
base.OnOpened(e);
Dispatcher.UIThread.Post(() => MainActionButton.Focus(), DispatcherPriority.Background);
_ = _vm.LoadDetailsAsync();
TelemetryHandler.PackageDetails(_vm.Package, _vm.OperationRole.ToString());
+
+ _installOpts = await InstallOptionsFactory.LoadForPackageAsync(_vm.Package);
+ _installVm = new InstallOptionsViewModel(_vm.Package, _vm.OperationRole, _installOpts);
+ var embed = new InstallOptionsControl();
+ embed.DataContext = _installVm;
+ InstallOptionsHolder.Content = embed;
+ }
+
+ private async Task SaveInstallOptionsAsync()
+ {
+ if (_installVm is null || _installOpts is null) return;
+ _installVm.ApplyChanges();
+ await InstallOptionsFactory.SaveForPackageAsync(_installOpts, _vm.Package);
}
private MenuFlyout BuildActionFlyout()
diff --git a/src/UniGetUI.Avalonia/Views/MainWindow.axaml b/src/UniGetUI.Avalonia/Views/MainWindow.axaml
index b60c8b722a..80ea09cc11 100644
--- a/src/UniGetUI.Avalonia/Views/MainWindow.axaml
+++ b/src/UniGetUI.Avalonia/Views/MainWindow.axaml
@@ -88,9 +88,67 @@
automation:AutomationProperties.Name="{t:Translate Operations}"
automation:AutomationProperties.LandmarkType="{x:Static peers:AutomationLandmarkType.Region}"
Background="{DynamicResource AppOperationsPanelBackground}">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -223,6 +281,7 @@
+
@@ -284,7 +343,8 @@
automation:AutomationProperties.AccessibilityView="Control"
Orientation="Horizontal"
Spacing="0">
- ViewModel.NavigateTo(type);
+ ///
+ /// Focuses the global search box and optionally pre-fills a character typed
+ /// while the package list had focus (type-to-search).
+ ///
+ public void FocusGlobalSearch(string prefill = "")
+ {
+ if (!string.IsNullOrEmpty(prefill))
+ {
+ ViewModel.GlobalSearchText = prefill;
+ // Place cursor at end so the user can keep typing
+ GlobalSearchBox.CaretIndex = prefill.Length;
+ }
+ GlobalSearchBox.Focus();
+ }
+
// ─── Public API (legacy compat) ───────────────────────────────────────────
public void ShowBanner(string title, string message, RuntimeNotificationLevel level)
{
diff --git a/src/UniGetUI.Avalonia/Views/Pages/HelpPage.axaml b/src/UniGetUI.Avalonia/Views/Pages/HelpPage.axaml
index 0578f663e4..43d37cd008 100644
--- a/src/UniGetUI.Avalonia/Views/Pages/HelpPage.axaml
+++ b/src/UniGetUI.Avalonia/Views/Pages/HelpPage.axaml
@@ -74,9 +74,21 @@
automation:AutomationProperties.AccessibilityView="Raw"/>
-
+
+
+
+
+
+
+
diff --git a/src/UniGetUI.Avalonia/Views/Pages/HelpPage.axaml.cs b/src/UniGetUI.Avalonia/Views/Pages/HelpPage.axaml.cs
index d18803b7e0..e68467488e 100644
--- a/src/UniGetUI.Avalonia/Views/Pages/HelpPage.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/Pages/HelpPage.axaml.cs
@@ -1,7 +1,6 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using UniGetUI.Avalonia.ViewModels.Pages;
-using UniGetUI.Avalonia.Views.Pages;
namespace UniGetUI.Avalonia.Views.Pages;
@@ -9,6 +8,7 @@ public partial class HelpPage : UserControl, IEnterLeaveListener
{
private readonly HelpPageViewModel _viewModel;
private string _pendingNavigation = HelpPageViewModel.HelpBaseUrl;
+ private bool _adapterReady;
public HelpPage()
{
@@ -16,36 +16,46 @@ public HelpPage()
DataContext = _viewModel;
InitializeComponent();
- WebViewControl.NavigationStarted += OnNavigationStarted;
- WebViewControl.NavigationCompleted += OnNavigationCompleted;
- }
+ if (OperatingSystem.IsLinux())
+ {
+ WebViewBorder.IsVisible = false;
+ LinuxFallbackPanel.IsVisible = true;
+ return;
+ }
- private void OnNavigationStarted(object? sender, WebViewNavigationStartingEventArgs e)
- {
- NavProgressBar.IsVisible = true;
- }
+ WebViewControl.NavigationStarted += (_, _) =>
+ NavProgressBar.IsVisible = true;
- private void OnNavigationCompleted(object? sender, WebViewNavigationCompletedEventArgs e)
- {
- NavProgressBar.IsVisible = false;
- _viewModel.CurrentUrl = WebViewControl.Source?.ToString() ?? HelpPageViewModel.HelpBaseUrl;
+ WebViewControl.NavigationCompleted += (_, e) =>
+ {
+ NavProgressBar.IsVisible = false;
+ _viewModel.CurrentUrl = WebViewControl.Source?.ToString() ?? HelpPageViewModel.HelpBaseUrl;
+ BackButton.IsEnabled = WebViewControl.CanGoBack;
+ ForwardButton.IsEnabled = WebViewControl.CanGoForward;
+ };
- BackButton.IsEnabled = WebViewControl.CanGoBack;
- ForwardButton.IsEnabled = WebViewControl.CanGoForward;
+ // WebView2 on Windows initializes asynchronously after the control is attached
+ // to the visual tree. Navigate() called before AdapterCreated is silently dropped.
+ // This mirrors WinUI's EnsureCoreWebView2Async() pattern.
+ WebViewControl.AdapterCreated += (_, _) =>
+ {
+ _adapterReady = true;
+ WebViewControl.Navigate(new Uri(_pendingNavigation));
+ };
}
public void NavigateTo(string uriAttachment)
{
string url = _viewModel.GetInitialUrl(uriAttachment);
- if (WebViewControl.IsLoaded)
+ _pendingNavigation = url;
+ if (_adapterReady)
WebViewControl.Navigate(new Uri(url));
- else
- _pendingNavigation = url;
}
public void OnEnter()
{
- WebViewControl.Navigate(new Uri(_pendingNavigation));
+ if (_adapterReady)
+ WebViewControl.Navigate(new Uri(_pendingNavigation));
}
public void OnLeave() { }
diff --git a/src/UniGetUI.Avalonia/Views/Pages/ReleaseNotesPage.axaml b/src/UniGetUI.Avalonia/Views/Pages/ReleaseNotesPage.axaml
index 33db9f7280..75437361bf 100644
--- a/src/UniGetUI.Avalonia/Views/Pages/ReleaseNotesPage.axaml
+++ b/src/UniGetUI.Avalonia/Views/Pages/ReleaseNotesPage.axaml
@@ -35,9 +35,21 @@
automation:AutomationProperties.AccessibilityView="Raw"/>
-
+
+
+
+
+
+
+
diff --git a/src/UniGetUI.Avalonia/Views/Pages/ReleaseNotesPage.axaml.cs b/src/UniGetUI.Avalonia/Views/Pages/ReleaseNotesPage.axaml.cs
index 842cc529b9..a0b5e5e04c 100644
--- a/src/UniGetUI.Avalonia/Views/Pages/ReleaseNotesPage.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/Pages/ReleaseNotesPage.axaml.cs
@@ -7,6 +7,7 @@ public partial class ReleaseNotesPage : UserControl, IEnterLeaveListener
{
private readonly ReleaseNotesPageViewModel _viewModel;
private bool _loaded;
+ private bool _adapterReady;
public ReleaseNotesPage()
{
@@ -14,17 +15,36 @@ public ReleaseNotesPage()
DataContext = _viewModel;
InitializeComponent();
- WebViewControl.NavigationStarted += (_, _) => NavProgressBar.IsVisible = true;
+ if (OperatingSystem.IsLinux())
+ {
+ WebViewBorder.IsVisible = false;
+ LinuxFallbackPanel.IsVisible = true;
+ return;
+ }
+
+ WebViewControl.NavigationStarted += (_, _) =>
+ NavProgressBar.IsVisible = true;
+
WebViewControl.NavigationCompleted += (_, e) =>
{
NavProgressBar.IsVisible = false;
_viewModel.CurrentUrl = WebViewControl.Source?.ToString() ?? _viewModel.ReleaseNotesUrl;
};
+
+ WebViewControl.AdapterCreated += (_, _) =>
+ {
+ _adapterReady = true;
+ if (!_loaded)
+ {
+ WebViewControl.Navigate(new Uri(_viewModel.ReleaseNotesUrl));
+ _loaded = true;
+ }
+ };
}
public void OnEnter()
{
- if (!_loaded)
+ if (!_loaded && _adapterReady)
{
WebViewControl.Navigate(new Uri(_viewModel.ReleaseNotesUrl));
_loaded = true;
diff --git a/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml b/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml
index 5fdf3b256a..d43b9d2404 100644
--- a/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml
+++ b/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml
@@ -248,19 +248,15 @@
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
@@ -420,10 +429,25 @@
-
+
+
+
+
+
-
+
-
+
+
+
+
+
@@ -578,12 +611,19 @@
automation:AutomationProperties.Name="{Binding Package.AutomationName}"
automation:AutomationProperties.ItemType="{t:Translate Package}"
Background="{DynamicResource AppBorderBrush}" Padding="4">
-
-
+
+
+
+
+
+
-
+
+
+
+
+
@@ -629,11 +679,27 @@
HorizontalAlignment="Left"
VerticalAlignment="Top"
Padding="0" Margin="-4,0,0,0"/>
-
+
+
+
+
+
+
-
+
diff --git a/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml.cs b/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml.cs
index daa61430fe..99bc90d2e7 100644
--- a/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml.cs
@@ -4,9 +4,13 @@
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Input.Platform;
+using Avalonia.Interactivity;
+using Avalonia.Layout;
+using Avalonia.Media;
using Avalonia.Threading;
using UniGetUI.Avalonia.ViewModels.Pages;
using UniGetUI.Avalonia.Views.Controls;
+using UniGetUI.Core.SettingsEngine;
using UniGetUI.Core.Tools;
using UniGetUI.PackageEngine.Interfaces;
using UniGetUI.PackageEngine.PackageClasses;
@@ -17,6 +21,9 @@ public abstract partial class AbstractPackagesPage : UserControl,
IKeyboardShortcutListener, IEnterLeaveListener, ISearchBoxPage
{
public PackagesPageViewModel ViewModel => (PackagesPageViewModel)DataContext!;
+ private readonly ContextMenu? _contextMenu;
+ private double _savedFilterPaneWidth = 220;
+ private bool _isOverlayMode;
protected AbstractPackagesPage(PackagesPageData data)
{
@@ -49,7 +56,10 @@ or nameof(PackagesPageViewModel.SortAscending))
SyncOrderByButtonName();
}
if (args.PropertyName is nameof(PackagesPageViewModel.IsFilterPaneOpen))
+ {
SyncFiltersButtonName();
+ UpdateFilterPaneColumn(ViewModel.IsFilterPaneOpen);
+ }
};
SyncFiltersButtonName();
SyncOrderByButtonName();
@@ -63,17 +73,54 @@ or nameof(PackagesPageViewModel.SortAscending))
// Keyboard shortcuts on the package list
PackageList.KeyDown += PackageList_KeyDown;
+ // Type-to-search: printable characters typed while the list is focused
+ // redirect focus + the typed character to the global search box.
+ PackageList.TextInput += PackageList_TextInput;
+
+ // Snap-close when splitter is dragged below the minimum (inline mode only).
+ // Using ColumnDefinition.WidthProperty fires every drag step, not just on release.
+ FilteringPanel.ColumnDefinitions[0]
+ .GetObservable(ColumnDefinition.WidthProperty)
+ .Subscribe(width =>
+ {
+ if (_isOverlayMode || !ViewModel.IsFilterPaneOpen) return;
+ if (width.IsAbsolute && width.Value >= 100)
+ {
+ _savedFilterPaneWidth = width.Value;
+ Settings.SetDictionaryItem(Settings.K.SidepanelWidths, ViewModel.PageName, (int)width.Value);
+ }
+ else if (width.IsAbsolute && width.Value < 100)
+ {
+ _savedFilterPaneWidth = 220;
+ ViewModel.IsFilterPaneOpen = false;
+ }
+ });
+
+ // Responsive: switch between inline and overlay modes based on content width.
+ FilteringPanel.GetObservable(BoundsProperty)
+ .Subscribe(bounds => OnFilteringPanelWidthChanged(bounds.Width));
+
+ // Overlay backdrop dismisses the filter pane when tapped.
+ FilterOverlayBackdrop.PointerPressed += (_, _) => ViewModel.IsFilterPaneOpen = false;
+
// Wire context menu (built by subclass)
- var contextMenu = GenerateContextMenu();
- if (contextMenu is not null)
+ _contextMenu = GenerateContextMenu();
+ if (_contextMenu is not null)
{
- PackageList.ContextMenu = contextMenu;
- contextMenu.Opening += (_, _) =>
+ PackageList.ContextMenu = _contextMenu;
+ _contextMenu.Opening += (_, _) =>
{
var pkg = SelectedItem;
if (pkg is not null) WhenShowingContextMenu(pkg);
};
}
+
+ // Restore per-page filter pane width from settings.
+ var savedWidth = Settings.GetDictionaryItem(Settings.K.SidepanelWidths, ViewModel.PageName);
+ if (savedWidth >= 100) _savedFilterPaneWidth = savedWidth;
+
+ // Apply the initial filter-pane state (AXAML defaults to 220px open).
+ UpdateFilterPaneColumn(ViewModel.IsFilterPaneOpen);
}
// ─── UI-only: focus the package list ─────────────────────────────────────
@@ -193,10 +240,7 @@ private void UpdateSortMenuChecks()
}
// ─── IKeyboardShortcutListener ────────────────────────────────────────────
- public void SearchTriggered()
- {
- // TODO: focus global search box
- }
+ public void SearchTriggered() => GetMainWindow()?.FocusGlobalSearch();
public void ReloadTriggered() => ViewModel.TriggerReload();
public void SelectAllTriggered() => ViewModel.ToggleSelectAll();
@@ -243,6 +287,79 @@ private void PackageList_KeyDown(object? sender, KeyEventArgs e)
}
}
+ private void PackageList_TextInput(object? sender, TextInputEventArgs e)
+ {
+ if (string.IsNullOrEmpty(e.Text)) return;
+
+ // Append the typed character to the current query and move focus to the search box
+ GetMainWindow()?.FocusGlobalSearch(ViewModel.GlobalQueryText + e.Text);
+ e.Handled = true;
+ }
+
+ // ─── Filter pane column width management ─────────────────────────────────
+
+ private void OnFilteringPanelWidthChanged(double width)
+ {
+ if (width <= 0) return; // layout not complete yet
+ bool shouldBeOverlay = width < 1000;
+ if (shouldBeOverlay == _isOverlayMode) return;
+
+ _isOverlayMode = shouldBeOverlay;
+
+ if (_isOverlayMode && ViewModel.IsFilterPaneOpen)
+ ViewModel.IsFilterPaneOpen = false; // collapse pane when entering overlay
+ else
+ UpdateFilterPaneColumn(ViewModel.IsFilterPaneOpen);
+ }
+
+ private void UpdateFilterPaneColumn(bool open)
+ {
+ if (FilteringPanel.ColumnDefinitions.Count < 2) return;
+
+ if (_isOverlayMode)
+ {
+ // Package list fills full width; filter pane and splitter take no space.
+ FilteringPanel.ColumnDefinitions[0].Width = new GridLength(0);
+ FilteringPanel.ColumnDefinitions[1].Width = new GridLength(0);
+
+ // Float the filter pane on top of the content when open.
+ Grid.SetColumnSpan(SidePanel, 3);
+ SidePanel.ZIndex = 10;
+ SidePanel.Width = _savedFilterPaneWidth;
+ SidePanel.HorizontalAlignment = HorizontalAlignment.Left;
+
+ // Semi-transparent backdrop covers the package list behind the pane.
+ FilterOverlayBackdrop.IsVisible = open;
+ }
+ else
+ {
+ // Inline mode: pane sits beside the package list.
+ Grid.SetColumnSpan(SidePanel, 1);
+ SidePanel.ZIndex = 0;
+ SidePanel.Width = double.NaN;
+ SidePanel.HorizontalAlignment = HorizontalAlignment.Stretch;
+ FilterOverlayBackdrop.IsVisible = false;
+
+ FilteringPanel.ColumnDefinitions[0].Width = open
+ ? new GridLength(_savedFilterPaneWidth)
+ : new GridLength(0);
+ FilteringPanel.ColumnDefinitions[1].Width = open
+ ? new GridLength(4)
+ : new GridLength(0);
+ }
+ }
+
+ // ─── Card overflow button (Grid / Icons view) ─────────────────────────────
+ private void CardOverflowButton_Click(object? sender, RoutedEventArgs e)
+ {
+ if (sender is not Button { DataContext: PackageWrapper wrapper }) return;
+ PackageList.SelectedItem = wrapper;
+ if (_contextMenu is null) return;
+ WhenShowingContextMenu(wrapper.Package);
+ _contextMenu.Open(sender as Control);
+ e.Handled = true;
+ }
+
// ─── Shared cross-page helpers ────────────────────────────────────────────
protected static MainWindow? GetMainWindow()
=> Application.Current?.ApplicationLifetime
diff --git a/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs b/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs
index 934d4efca7..e602a8c069 100644
--- a/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs
+++ b/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs
@@ -63,6 +63,8 @@ public InstalledPackagesPage() : base(new PackagesPageData
_hasBackedUp = true;
if (Settings.Get(Settings.K.EnablePackageBackup_LOCAL))
_ = BackupViewModel.DoLocalBackupStatic();
+ if (Settings.Get(Settings.K.EnablePackageBackup_CLOUD))
+ _ = BackupViewModel.DoCloudBackupStatic();
}
if (OperatingSystem.IsWindows()
diff --git a/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs b/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs
index e1645f3b57..14ec931858 100644
--- a/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs
+++ b/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs
@@ -396,6 +396,11 @@ private static async Task WhenPackagesLoaded()
Logger.Warn("Updates will not be installed automatically because battery saver is enabled.");
ShowAvailableUpdatesNotification(upgradable);
}
+ else if (Settings.Get(Settings.K.DisableAUPOnMeteredConnections) && IsOnMeteredConnection())
+ {
+ Logger.Warn("Updates will not be installed automatically because the current internet connection is metered.");
+ ShowAvailableUpdatesNotification(upgradable);
+ }
else if (Settings.Get(Settings.K.AutomaticallyUpdatePackages))
{
_ = AvaloniaPackageOperationHelper.UpdateAllAsync();
@@ -473,4 +478,18 @@ private static bool IsBatterySaverOn()
return GetSystemPowerStatus(out var s) && (s.SystemStatusFlag & 0x01) != 0;
#pragma warning restore CA1416
}
+
+ private static bool IsOnMeteredConnection()
+ {
+#if WINDOWS
+ var costType = Windows.Networking.Connectivity.NetworkInformation
+ .GetInternetConnectionProfile()
+ ?.GetConnectionCost()
+ .NetworkCostType;
+ return costType is Windows.Networking.Connectivity.NetworkCostType.Fixed
+ or Windows.Networking.Connectivity.NetworkCostType.Variable;
+#else
+ return false;
+#endif
+ }
}
diff --git a/src/UniGetUI.Avalonia/app.manifest b/src/UniGetUI.Avalonia/app.manifest
index a4c7b5a162..16f839599f 100644
--- a/src/UniGetUI.Avalonia/app.manifest
+++ b/src/UniGetUI.Avalonia/app.manifest
@@ -7,12 +7,18 @@
-
-
-
+
+
+
+
+
+ PerMonitorV2, PerMonitor
+ true
+
+
+