Skip to content
Open
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
@@ -0,0 +1,29 @@
using System.Runtime.ExceptionServices;

namespace UniGetUI.Avalonia.Extensions;

internal static class ObservableSubscriptionExtensions
{
public static IDisposable SubscribeValue<T>(this IObservable<T> source, Action<T> onNext)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(onNext);

return source.Subscribe(new ActionObserver<T>(onNext));
}

private sealed class ActionObserver<T>(Action<T> onNext) : IObserver<T>
{
public void OnCompleted()
{
}

public void OnError(Exception error)
{
ExceptionDispatchInfo.Capture(error).Throw();
}

public void OnNext(T value)
=> onNext(value);
}
}
4 changes: 2 additions & 2 deletions src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,8 @@
<PackageReference Include="Avalonia.Controls.DataGrid" Version="12.0.0" />
<PackageReference Include="Avalonia.Desktop" Version="12.0.4" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.0.4" />
<PackageReference Include="Devolutions.AvaloniaTheme.MacOS" Version="2026.3.13" />
<PackageReference Include="Devolutions.AvaloniaTheme.Linux" Version="2026.3.11" />
<PackageReference Include="Devolutions.AvaloniaTheme.MacOS" Version="2026.3.13" Condition="$([MSBuild]::IsOSPlatform('OSX'))" />
<PackageReference Include="Devolutions.AvaloniaTheme.Linux" Version="2026.3.11" Condition="$([MSBuild]::IsOSPlatform('Linux'))" />
<PackageReference Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.0-beta3" Condition="'$(EnableAvaloniaDiagnostics)' == 'true'" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.*" />
<PackageReference Include="Octokit" Version="14.0.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Media;
using UniGetUI.Avalonia.Extensions;
using UniGetUI.Avalonia.Infrastructure;
using UniGetUI.Core.Logging;
using UniGetUI.Core.SettingsEngine.SecureSettings;
Expand Down Expand Up @@ -134,7 +135,7 @@ public SecureCheckboxCard()
_checkbox.IsCheckedChanged += (s, e) => _ = _checkbox_Toggled();

this.GetObservable(IsEnabledProperty)
.Subscribe(enabled => _warningBlock.Opacity = enabled ? 1 : 0.2);
.SubscribeValue(enabled => _warningBlock.Opacity = enabled ? 1 : 0.2);

// The Devolutions SettingsCard measures the Header with infinite width, so
// TextWrapping alone won't constrain the warning block. We fix it by updating
Expand Down
11 changes: 6 additions & 5 deletions src/UniGetUI.Avalonia/Views/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Avalonia.Media;
using Avalonia.Threading;
using Avalonia.VisualTree;
using UniGetUI.Avalonia.Extensions;
using UniGetUI.Avalonia.Infrastructure;
using UniGetUI.Avalonia.ViewModels;
using UniGetUI.Avalonia.Views.Pages;
Expand Down Expand Up @@ -128,7 +129,7 @@

Resized += (_, _) => _ = SaveGeometryAsync();
PositionChanged += (_, _) => _ = SaveGeometryAsync();
this.GetObservable(WindowStateProperty).Subscribe(state => { _ = SaveGeometryAsync(); });
this.GetObservable(WindowStateProperty).SubscribeValue(state => { _ = SaveGeometryAsync(); });

_trayService = new TrayService(this);
_trayService.UpdateStatus();
Expand Down Expand Up @@ -290,7 +291,7 @@
// windows give the content full width (the hamburger + sliding flyout still provide nav).
private void SetupResponsiveRail()
=> MainContentRoot.GetObservable(BoundsProperty)
.Subscribe(b =>
.SubscribeValue(b =>
{
if (b.Width <= 0) return;
NavRail.IsVisible = b.Width >= 800;
Expand All @@ -314,7 +315,7 @@
// collapses to 0, which would clip the search box and hamburger. Use a fixed
// title bar height in that state, and drop the traffic-light reservation
// since the traffic lights aren't shown either.
this.GetObservable(WindowStateProperty).Subscribe(state =>
this.GetObservable(WindowStateProperty).SubscribeValue(state =>
{
if (state == WindowState.FullScreen)
{
Expand All @@ -326,11 +327,11 @@
}
else
{
TitleBarGrid.Bind(HeightProperty, new Binding("WindowDecorationMargin.Top")

Check warning on line 330 in src/UniGetUI.Avalonia/Views/MainWindow.axaml.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Using member 'Avalonia.Data.Binding.Binding(String)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. BindingExpression and ReflectionBinding heavily use reflection. Consider using CompiledBindings instead.
{
RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor) { AncestorType = typeof(Window) },
});
MainContentRoot.Bind(MarginProperty, new Binding("WindowDecorationMargin")

Check warning on line 334 in src/UniGetUI.Avalonia/Views/MainWindow.axaml.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Using member 'Avalonia.Data.Binding.Binding(String)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. BindingExpression and ReflectionBinding heavily use reflection. Consider using CompiledBindings instead.
{
RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor) { AncestorType = typeof(Window) },
});
Expand All @@ -354,7 +355,7 @@
HamburgerPanel.Margin = new Thickness(10, 0, 8, 0);
WindowButtons.IsVisible = true;
MainContentRoot.Margin = new Thickness(0, 44, 0, 0);
this.GetObservable(WindowStateProperty).Subscribe(state =>
this.GetObservable(WindowStateProperty).SubscribeValue(state =>
{
UpdateMaximizeButtonState(state == WindowState.Maximized);
});
Expand All @@ -372,7 +373,7 @@
WindowButtons.IsVisible = !useNativeDecorations;
MainContentRoot.Margin = new Thickness(0, 44, 0, 0);
// Keep maximize icon in sync with window state
this.GetObservable(WindowStateProperty).Subscribe(state =>
this.GetObservable(WindowStateProperty).SubscribeValue(state =>
{
UpdateMaximizeButtonState(state == WindowState.Maximized);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Avalonia.Media;
using Avalonia.Media.Transformation;
using Avalonia.Threading;
using UniGetUI.Avalonia.Extensions;
using UniGetUI.Avalonia.ViewModels.Pages;
using UniGetUI.Avalonia.Views.Controls;
using UniGetUI.Core.SettingsEngine;
Expand Down Expand Up @@ -95,7 +96,7 @@ or nameof(PackagesPageViewModel.SortAscending))
// Using ColumnDefinition.WidthProperty fires every drag step, not just on release.
FilteringPanel.ColumnDefinitions[0]
.GetObservable(ColumnDefinition.WidthProperty)
.Subscribe(width =>
.SubscribeValue(width =>
{
if (_isOverlayMode || !ViewModel.IsFilterPaneOpen) return;
if (width.IsAbsolute && width.Value >= 100)
Expand All @@ -114,19 +115,19 @@ or nameof(PackagesPageViewModel.SortAscending))

// Responsive: switch between inline and overlay modes based on content width.
FilteringPanel.GetObservable(BoundsProperty)
.Subscribe(bounds => OnFilteringPanelWidthChanged(bounds.Width));
.SubscribeValue(bounds => OnFilteringPanelWidthChanged(bounds.Width));

// Responsive: collapse the menu bar to icon-only on narrow windows so the
// toolbar buttons stay reachable instead of overflowing (mirrors WinUI).
this.GetObservable(BoundsProperty)
.Subscribe(bounds => ViewModel.SetToolbarLabelsCollapsed(bounds.Width < 900));
.SubscribeValue(bounds => ViewModel.SetToolbarLabelsCollapsed(bounds.Width < 900));

// Grid/icons views: stretch cards to fill each row then reflow (mirrors WinUI's
// UniformGridLayout) instead of leaving wasted space to the right.
GridViewItems.GetObservable(BoundsProperty)
.Subscribe(bounds => UpdateGridCardWidth(bounds.Width));
.SubscribeValue(bounds => UpdateGridCardWidth(bounds.Width));
IconsViewItems.GetObservable(BoundsProperty)
.Subscribe(bounds => UpdateIconCardWidth(bounds.Width));
.SubscribeValue(bounds => UpdateIconCardWidth(bounds.Width));

// Overlay backdrop dismisses the filter pane when tapped.
FilterOverlayBackdrop.PointerPressed += (_, _) => ViewModel.IsFilterPaneOpen = false;
Expand Down
Loading