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
@@ -1,9 +1,14 @@
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.Pages.SettingsPages;
using UniGetUI.Core.Tools;
using CoreSettings = global::UniGetUI.Core.SettingsEngine.Settings;
using UniGetUI.PackageEngine;
using CoreSettings = UniGetUI.Core.SettingsEngine.Settings;
using CornerRadius = Avalonia.CornerRadius;
using Thickness = Avalonia.Thickness;

namespace UniGetUI.Avalonia.ViewModels.Pages.SettingsPages;

Expand All @@ -13,6 +18,23 @@ public partial class UpdatesViewModel : ViewModelBase
public event EventHandler<Type>? NavigationRequested;

[ObservableProperty] private bool _isAutoCheckEnabled;
[ObservableProperty] private bool _isCustomAgeSelected;

private static readonly HashSet<string> _managersWithoutUpdateDate =
new(StringComparer.OrdinalIgnoreCase)
{ "Homebrew", "Scoop", "vcpkg" };

/// <summary>Items for the minimum update age ComboboxCard, in display/value pairs.</summary>
public IReadOnlyList<(string Name, string Value)> MinimumAgeItems { get; } =
[
(CoreTools.Translate("No minimum age"), "0"),
(CoreTools.Translate("1 day"), "1"),
(CoreTools.Translate("{0} days", 3), "3"),
(CoreTools.Translate("{0} days", 7), "7"),
(CoreTools.Translate("{0} days", 14), "14"),
(CoreTools.Translate("{0} days", 30), "30"),
(CoreTools.Translate("Custom..."), "custom"),
];

/// <summary>Items for the update interval ComboboxCard, in display/value pairs.</summary>
public IReadOnlyList<(string Name, string Value)> IntervalItems { get; } =
Expand All @@ -33,8 +55,85 @@ public partial class UpdatesViewModel : ViewModelBase
public UpdatesViewModel()
{
_isAutoCheckEnabled = !CoreSettings.Get(CoreSettings.K.DisableAutoCheckforUpdates);
_isCustomAgeSelected = CoreSettings.GetValue(CoreSettings.K.MinimumUpdateAge) == "custom";
}

public Control BuildReleaseDateCompatTable()
{
string yesStr = CoreTools.Translate("Yes");
string noStr = CoreTools.Translate("No");

var managers = PEInterface.Managers.ToList();

var table = new Grid
{
ColumnDefinitions = new ColumnDefinitions("Auto,Auto"),
ColumnSpacing = 24,
RowSpacing = 8,
};
for (int i = 0; i <= managers.Count; i++)
table.RowDefinitions.Add(new RowDefinition(GridLength.Auto));

var h1 = new TextBlock { Text = CoreTools.Translate("Package manager"), FontWeight = FontWeight.Bold };
var h2 = new TextBlock { Text = CoreTools.Translate("Supports release dates"), FontWeight = FontWeight.Bold, HorizontalAlignment = HorizontalAlignment.Center };
Grid.SetRow(h1, 0); Grid.SetColumn(h1, 0);
Grid.SetRow(h2, 0); Grid.SetColumn(h2, 1);
table.Children.Add(h1);
table.Children.Add(h2);

for (int i = 0; i < managers.Count; i++)
{
var manager = managers[i];
int row = i + 1;

var name = new TextBlock { Text = manager.DisplayName, VerticalAlignment = VerticalAlignment.Center };
Grid.SetRow(name, row); Grid.SetColumn(name, 0);

bool supported = !_managersWithoutUpdateDate.Contains(manager.Name);
var badge = _statusBadge(supported ? yesStr : noStr, supported ? Colors.Green : Colors.Red);
Grid.SetRow(badge, row); Grid.SetColumn(badge, 1);

table.Children.Add(name);
table.Children.Add(badge);
}

var title = new TextBlock
{
Text = CoreTools.Translate("Release date support per package manager"),
FontWeight = FontWeight.SemiBold,
Margin = new Thickness(0, 0, 0, 12),
};

var centerWrapper = new Grid { ColumnDefinitions = new ColumnDefinitions("*,Auto,*") };
Grid.SetColumn(table, 1);
centerWrapper.Children.Add(table);

var stack = new StackPanel { Orientation = Orientation.Vertical };
stack.Children.Add(title);
stack.Children.Add(centerWrapper);

var border = new Border
{
CornerRadius = new CornerRadius(8),
BorderThickness = new Thickness(1),
Padding = new Thickness(16, 12),
Child = stack,
};
border.Classes.Add("settings-card");
return border;
}

private static Border _statusBadge(string text, Color color) => new Border
{
CornerRadius = new CornerRadius(4),
Padding = new Thickness(4, 2),
BorderThickness = new Thickness(1),
HorizontalAlignment = HorizontalAlignment.Stretch,
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 },
};

[RelayCommand]
private void UpdateAutoCheckEnabled()
{
Expand Down
13 changes: 13 additions & 0 deletions src/UniGetUI.Avalonia/Views/Controls/Settings/TextboxCard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public CoreSettings.K SettingName
}
}

public bool IsNumericOnly { get; set; }

public string Placeholder
{
set => _textbox.Watermark = value;
Expand Down Expand Up @@ -83,6 +85,17 @@ public void SaveValue()
{
string sanitizedText = _textbox.Text ?? "";

if (IsNumericOnly)
{
string filtered = string.Concat(sanitizedText.Where(char.IsDigit));
if (filtered != sanitizedText)
{
_textbox.Text = filtered; // triggers TextChanged → SaveValue again with clean text
return;
}
sanitizedText = filtered;
}

if (CoreSettings.ResolveKey(setting_name).Contains("File"))
sanitizedText = CoreTools.MakeValidFileName(sanitizedText);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ namespace UniGetUI.Avalonia.Views.Pages.SettingsPages;

public sealed partial class PackageManagerPage : UserControl, ISettingsPage
{
private static readonly HashSet<string> _managersWithoutUpdateDate =
new(StringComparer.OrdinalIgnoreCase)
{ "Homebrew", "Scoop", "vcpkg" };
private PackageManagerViewModel ViewModel => (PackageManagerViewModel)DataContext!;

public bool CanGoBack => true;
Expand Down Expand Up @@ -197,6 +200,113 @@ private void BuildPage()

BuildExtraControls(disableNotifsCard);

// ── Per-manager minimum update age
ExtraControls.Children.Add(new TextBlock
{
Margin = new Thickness(44, 24, 4, 8),
FontWeight = FontWeight.SemiBold,
Text = CoreTools.Translate("Update security"),
});

(string Label, string Value)[] ageItems =
[
(CoreTools.Translate("Use global setting"), ""),
(CoreTools.Translate("No minimum age"), "0"),
(CoreTools.Translate("1 day"), "1"),
(CoreTools.Translate("{0} days", 3), "3"),
(CoreTools.Translate("{0} days", 7), "7"),
(CoreTools.Translate("{0} days", 14), "14"),
(CoreTools.Translate("{0} days", 30), "30"),
(CoreTools.Translate("Custom..."), "custom"),
];

var ageCombo = new ComboBox { MinWidth = 200 };
foreach (var (label, _) in ageItems)
ageCombo.Items.Add(label);

string? savedAge = CoreSettings.GetDictionaryItem<string, string>(
CoreSettings.K.PerManagerMinimumUpdateAge, manager.Name);
int savedAgeIdx = Array.FindIndex(ageItems, i => i.Value == (savedAge ?? ""));
ageCombo.SelectedIndex = savedAgeIdx >= 0 ? savedAgeIdx : 0;

var customAgeInput = new TextBox
{
MinWidth = 200,
Watermark = CoreTools.Translate("e.g. 10"),
Text = CoreSettings.GetDictionaryItem<string, string>(
CoreSettings.K.PerManagerMinimumUpdateAgeCustom, manager.Name) ?? "",
};
customAgeInput.TextChanged += (_, _) =>
{
string current = customAgeInput.Text ?? "";
string filtered = string.Concat(current.Where(char.IsDigit));
if (filtered != current)
{
customAgeInput.Text = filtered;
return;
}
if (filtered.Length > 0)
CoreSettings.SetDictionaryItem(
CoreSettings.K.PerManagerMinimumUpdateAgeCustom, manager.Name, filtered);
else
CoreSettings.RemoveDictionaryKey<string, string>(
CoreSettings.K.PerManagerMinimumUpdateAgeCustom, manager.Name);
};

bool initiallyCustom = savedAge == "custom";
bool ageSupported = !_managersWithoutUpdateDate.Contains(manager.Name);
object ageDescription = !ageSupported
? new TextBlock
{
Text = CoreTools.Translate("{pm} does not provide release dates for its packages, so this setting will have no effect")
.Replace("{pm}", manager.DisplayName),
Foreground = new SolidColorBrush(Color.Parse("#e05252")),
TextWrapping = TextWrapping.Wrap,
FontSize = 12,
}
: CoreTools.Translate("Override the global minimum update age for this package manager");

ageCombo.IsEnabled = ageSupported;
customAgeInput.IsEnabled = ageSupported;

var minimumAgeCard = new SettingsCard
{
Header = CoreTools.Translate("Minimum age for updates"),
Description = ageDescription,
Content = ageCombo,
CornerRadius = initiallyCustom ? new CornerRadius(8, 8, 0, 0) : new CornerRadius(8),
BorderThickness = new Thickness(1),
};
var customAgeCard = new SettingsCard
{
Header = CoreTools.Translate("Custom minimum age (days)"),
Content = customAgeInput,
IsVisible = initiallyCustom,
CornerRadius = new CornerRadius(0, 0, 8, 8),
BorderThickness = new Thickness(1, 0, 1, 1),
};

ageCombo.SelectionChanged += (_, _) =>
{
int idx = ageCombo.SelectedIndex;
if (idx < 0) return;
string val = ageItems[idx].Value;

bool isCustom = val == "custom";
customAgeCard.IsVisible = isCustom;
minimumAgeCard.CornerRadius = isCustom ? new CornerRadius(8, 8, 0, 0) : new CornerRadius(8);

if (string.IsNullOrEmpty(val))
CoreSettings.RemoveDictionaryKey<string, string>(
CoreSettings.K.PerManagerMinimumUpdateAge, manager.Name);
else
CoreSettings.SetDictionaryItem(
CoreSettings.K.PerManagerMinimumUpdateAge, manager.Name, val);
};

ExtraControls.Children.Add(minimumAgeCard);
ExtraControls.Children.Add(customAgeCard);

// ── Logs card
ManagerLogs.Text = CoreTools.Translate("View {0} logs", manager.DisplayName);
ManagerLogs.Icon = UniGetUI.Interface.Enums.IconType.Console;
Expand Down
22 changes: 22 additions & 0 deletions src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Updates.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,28 @@
CornerRadius="0,0,8,8"
IsEnabled="{Binding IsAutoCheckEnabled}"/>

<settings:TranslatedTextBlock Text="Update security"
FontWeight="SemiBold"
Margin="44,32,4,8"/>

<settings:ComboboxCard x:Name="MinimumUpdateAgeSelector"
SettingName="MinimumUpdateAge"
Text="{t:Translate Minimum age for updates}"
Description="{t:Translate Only show updates that are at least the specified number of days old}"
CornerRadius="8,8,8,8"/>

<settings:TextboxCard x:Name="MinimumUpdateAgeCustomInput"
SettingName="MinimumUpdateAgeCustom"
Text="{t:Translate Custom minimum age (days)}"
Placeholder="{t:Translate e.g. 10}"
IsNumericOnly="True"
IsVisible="{Binding IsCustomAgeSelected}"
BorderThickness="1,0,1,1"
CornerRadius="0,0,8,8"/>

<ContentControl x:Name="ReleaseDateCompatTableHolder"
Margin="0,8,0,0"/>

<settings:TranslatedTextBlock Text="Related settings"
FontWeight="SemiBold"
Margin="44,32,4,8"/>
Expand Down
20 changes: 20 additions & 0 deletions src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Updates.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Avalonia.Controls;
using UniGetUI.Avalonia.ViewModels.Pages.SettingsPages;
using UniGetUI.Core.Tools;
using CoreSettings = global::UniGetUI.Core.SettingsEngine.Settings;
using CornerRadius = global::Avalonia.CornerRadius;

namespace UniGetUI.Avalonia.Views.Pages.SettingsPages;

Expand All @@ -25,5 +27,23 @@ public Updates()
foreach (var (name, val) in VM.IntervalItems)
UpdatesCheckIntervalSelector.AddItem(name, val, false);
UpdatesCheckIntervalSelector.ShowAddedItems();

foreach (var (name, val) in VM.MinimumAgeItems)
MinimumUpdateAgeSelector.AddItem(name, val, false);
MinimumUpdateAgeSelector.ShowAddedItems();

MinimumUpdateAgeSelector.ValueChanged += (_, _) => RefreshMinimumAgeLayout();
RefreshMinimumAgeLayout();

ReleaseDateCompatTableHolder.Content = VM.BuildReleaseDateCompatTable();
}

private void RefreshMinimumAgeLayout()
{
bool isCustom = CoreSettings.GetValue(CoreSettings.K.MinimumUpdateAge) == "custom";
VM.IsCustomAgeSelected = isCustom;
MinimumUpdateAgeSelector.CornerRadius = isCustom
? new CornerRadius(8, 8, 0, 0)
: new CornerRadius(8);
}
}
Loading
Loading