diff --git a/src/OneWare.Essentials/Controls/SegmentedControl.axaml b/src/OneWare.Essentials/Controls/SegmentedControl.axaml
new file mode 100644
index 000000000..da14375d4
--- /dev/null
+++ b/src/OneWare.Essentials/Controls/SegmentedControl.axaml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/OneWare.Essentials/Controls/SegmentedControl.cs b/src/OneWare.Essentials/Controls/SegmentedControl.cs
new file mode 100644
index 000000000..b19430b06
--- /dev/null
+++ b/src/OneWare.Essentials/Controls/SegmentedControl.cs
@@ -0,0 +1,70 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Mixins;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
+using Avalonia.Input;
+using Avalonia.Layout;
+
+namespace OneWare.Essentials.Controls;
+
+///
+/// A generic single-selection segmented control that lays its items out horizontally.
+/// Items can be plain objects or instances.
+///
+public class SegmentedControl : SelectingItemsControl
+{
+ private static readonly FuncTemplate DefaultPanel =
+ new(() => new StackPanel { Orientation = Orientation.Horizontal });
+
+ static SegmentedControl()
+ {
+ SelectionModeProperty.OverrideDefaultValue(
+ SelectionMode.Single | SelectionMode.AlwaysSelected);
+ ItemsPanelProperty.OverrideDefaultValue(DefaultPanel);
+ }
+
+ protected override Type StyleKeyOverride => typeof(SegmentedControl);
+
+ protected override void OnPointerPressed(PointerPressedEventArgs e)
+ {
+ base.OnPointerPressed(e);
+
+ if (e.Source is Visual source && e.GetCurrentPoint(source).Properties.IsLeftButtonPressed)
+ e.Handled = UpdateSelectionFromEventSource(e.Source);
+ }
+
+ protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
+ {
+ return new SegmentedControlItem();
+ }
+
+ protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
+ {
+ return NeedsContainer(item, out recycleKey);
+ }
+}
+
+///
+/// Container for an item hosted inside a .
+///
+public class SegmentedControlItem : ContentControl, ISelectable
+{
+ public static readonly StyledProperty IsSelectedProperty =
+ SelectingItemsControl.IsSelectedProperty.AddOwner();
+
+ static SegmentedControlItem()
+ {
+ SelectableMixin.Attach(IsSelectedProperty);
+ PressedMixin.Attach();
+ FocusableProperty.OverrideDefaultValue(true);
+ }
+
+ public bool IsSelected
+ {
+ get => GetValue(IsSelectedProperty);
+ set => SetValue(IsSelectedProperty, value);
+ }
+
+ protected override Type StyleKeyOverride => typeof(SegmentedControlItem);
+}
diff --git a/src/OneWare.Essentials/Controls/SharedControls.axaml b/src/OneWare.Essentials/Controls/SharedControls.axaml
index 661ef1aba..40bd6c68f 100644
--- a/src/OneWare.Essentials/Controls/SharedControls.axaml
+++ b/src/OneWare.Essentials/Controls/SharedControls.axaml
@@ -6,5 +6,6 @@
+
\ No newline at end of file
diff --git a/src/OneWare.Essentials/Services/IPackageService.cs b/src/OneWare.Essentials/Services/IPackageService.cs
index 4110e1047..8dc386b2a 100644
--- a/src/OneWare.Essentials/Services/IPackageService.cs
+++ b/src/OneWare.Essentials/Services/IPackageService.cs
@@ -41,6 +41,13 @@ public interface IPackageService : INotifyPropertyChanged
///
void RegisterPackageRepository(string url);
+ ///
+ /// Registers a package repository URL with fallback URLs.
+ /// Uses the first URL of the List that works.
+ ///
+ ///
+ void RegisterPackageRepository(List urls);
+
///
/// Registers a package installer for a package type.
///
diff --git a/src/OneWare.PackageManager/Services/IPackageCatalog.cs b/src/OneWare.PackageManager/Services/IPackageCatalog.cs
index 78a7b2815..422b62e34 100644
--- a/src/OneWare.PackageManager/Services/IPackageCatalog.cs
+++ b/src/OneWare.PackageManager/Services/IPackageCatalog.cs
@@ -6,7 +6,7 @@ public interface IPackageCatalog
{
IReadOnlyDictionary Manifests { get; }
- Task RefreshAsync(IEnumerable sources, CancellationToken cancellationToken = default);
+ Task RefreshAsync(IEnumerable> sources, CancellationToken cancellationToken = default);
void RegisterStandalone(Package package);
}
diff --git a/src/OneWare.PackageManager/Services/PackageCatalog.cs b/src/OneWare.PackageManager/Services/PackageCatalog.cs
index 0602863d5..c89a0bb6e 100644
--- a/src/OneWare.PackageManager/Services/PackageCatalog.cs
+++ b/src/OneWare.PackageManager/Services/PackageCatalog.cs
@@ -26,27 +26,36 @@ public void RegisterStandalone(Package package)
_manifests[package.Id] = package;
}
- public async Task RefreshAsync(IEnumerable sources, CancellationToken cancellationToken = default)
+ public async Task RefreshAsync(IEnumerable> sources, CancellationToken cancellationToken = default)
{
var result = true;
var newPackages = new Dictionary();
- foreach (var source in sources)
+ foreach (var I in sources)
{
- IReadOnlyList loaded;
- try
- {
- loaded = await _repositoryClient.LoadRepositoryAsync(source, cancellationToken);
- }
- catch (OperationCanceledException)
- {
- throw;
- }
- catch (Exception e)
+
+ IReadOnlyList loaded = new List();
+ foreach (var source in I)
{
- _logger.Error($"Failed to refresh package source '{source}'.", e);
- result = false;
- continue;
+ try
+ {
+ loaded = await _repositoryClient.LoadRepositoryAsync(source, cancellationToken);
+ if(loaded.Count == 0)
+ {
+ continue;
+ }
+ break;
+ }
+ catch (OperationCanceledException)
+ {
+ throw;
+ }
+ catch (Exception e)
+ {
+ _logger.Error($"Failed to refresh package source '{source}'.", e);
+ result = false;
+ continue;
+ }
}
if (loaded.Count == 0)
diff --git a/src/OneWare.PackageManager/Services/PackageService.cs b/src/OneWare.PackageManager/Services/PackageService.cs
index d38f40b98..82a7920e0 100644
--- a/src/OneWare.PackageManager/Services/PackageService.cs
+++ b/src/OneWare.PackageManager/Services/PackageService.cs
@@ -31,7 +31,7 @@ public class PackageService : ObservableObject, IPackageService
private readonly Dictionary _installersByType = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary> _activeInstalls = new();
private readonly Dictionary _installCancellation = new();
- private readonly List _repositoryUrls = [];
+ private readonly List> _repositoryUrls = [];
private Task? _currentRefreshTask;
@@ -96,7 +96,12 @@ public void RegisterPackage(Package package)
public void RegisterPackageRepository(string url)
{
- _repositoryUrls.Add(url);
+ _repositoryUrls.Add(new List { url });
+ }
+
+ public void RegisterPackageRepository(List urls)
+ {
+ _repositoryUrls.Add(urls);
}
public void RegisterInstaller(string packageType) where T : IPackageInstaller
@@ -299,8 +304,10 @@ private async Task RefreshInternalAsync()
try
{
- var customRepositories =
- _settingsService.GetSettingValue>("PackageManager_Sources");
+ var customRepositories = _settingsService
+ .GetSettingValue>("PackageManager_Sources")
+ .Select(item => new List { item })
+ .ToList();
var allRepos = _repositoryUrls.Concat(customRepositories);
result = await _catalog.RefreshAsync(allRepos);
diff --git a/src/OneWare.PackageManager/ViewModels/PackageCategoryViewModel.cs b/src/OneWare.PackageManager/ViewModels/PackageCategoryViewModel.cs
index 13a1eb253..62173bc80 100644
--- a/src/OneWare.PackageManager/ViewModels/PackageCategoryViewModel.cs
+++ b/src/OneWare.PackageManager/ViewModels/PackageCategoryViewModel.cs
@@ -1,4 +1,4 @@
-using System.Collections.ObjectModel;
+using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using DynamicData;
using OneWare.Essentials.Enums;
@@ -27,6 +27,8 @@ public PackageViewModel? SelectedPackage
public ObservableCollection VisiblePackages { get; } = [];
+ public ObservableCollection