Skip to content

Commit 47dc9d3

Browse files
feat(avalonia): experimental cross-platform Avalonia port
Introduce an experimental Avalonia UI implementation, infrastructure, pages, and settings flows.\n\nAlso includes:\n- single-instance forwarding and startup/dependency handling\n- auto-update gating (battery, battery-saver, metered)\n- translation key alignment and Avalonia warning cleanup\n- CI updates to validate Avalonia formatting/style/build separately
1 parent 2265e17 commit 47dc9d3

105 files changed

Lines changed: 21646 additions & 4 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/dotnet-test.yml

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ on:
55
paths:
66
- 'global.json'
77
- '**.cs'
8+
- '**.axaml'
89
- '**.csproj'
910
- '**.props'
1011
- '**.targets'
1112
- '**.sln'
13+
- '**.slnx'
1214
- 'src/.editorconfig'
1315
- '.github/workflows/dotnet-test.yml'
1416

@@ -17,10 +19,12 @@ on:
1719
paths:
1820
- 'global.json'
1921
- '**.cs'
22+
- '**.axaml'
2023
- '**.csproj'
2124
- '**.props'
2225
- '**.targets'
2326
- '**.sln'
27+
- '**.slnx'
2428
- 'src/.editorconfig'
2529
- '.github/workflows/dotnet-test.yml'
2630

@@ -47,7 +51,7 @@ jobs:
4751
uses: actions/cache@v5
4852
with:
4953
path: ${{ env.NUGET_PACKAGES }}
50-
key: ${{ runner.os }}-nuget-${{ hashFiles('global.json', 'src/**/*.csproj', 'src/**/*.props', 'src/**/*.targets', 'src/**/*.sln') }}
54+
key: ${{ runner.os }}-nuget-${{ hashFiles('global.json', 'src/**/*.csproj', 'src/**/*.props', 'src/**/*.targets', 'src/**/*.sln', 'src/**/*.slnx') }}
5155
restore-keys: |
5256
${{ runner.os }}-nuget-
5357
@@ -75,15 +79,25 @@ jobs:
7579

7680
- name: Install dependencies
7781
working-directory: src
78-
run: dotnet restore UniGetUI.sln
82+
run: |
83+
dotnet restore UniGetUI.sln
84+
dotnet restore UniGetUI.Avalonia.slnx
7985
8086
- name: Check whitespace formatting
8187
run: dotnet format whitespace src --folder --verify-no-changes --verbosity minimal
8288

83-
- name: Check code style formatting
89+
- name: Check code style formatting (WinUI solution)
8490
working-directory: src
8591
run: dotnet format style UniGetUI.sln --no-restore --verify-no-changes --verbosity minimal
8692

93+
- name: Check code style formatting (Avalonia solution)
94+
working-directory: src
95+
run: dotnet format style UniGetUI.Avalonia.slnx --no-restore --verify-no-changes --verbosity minimal
96+
97+
- name: Build Avalonia solution
98+
working-directory: src
99+
run: dotnet build UniGetUI.Avalonia.slnx --no-restore --verbosity minimal
100+
87101
- name: Run Tests
88102
working-directory: src
89103
env:

src/UniGetUI.Avalonia.slnx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
<Solution>
22
<Configurations>
3-
<Platform Name="arm64" />
43
<Platform Name="x64" />
4+
<Platform Name="arm64" />
55
</Configurations>
6+
<Folder Name="/UniGetUI.Avalonia/">
7+
<Project Path="UniGetUI.Avalonia/UniGetUI.Avalonia.csproj">
8+
<Platform Solution="*|x64" Project="x64" />
9+
<Platform Solution="*|arm64" Project="arm64" />
10+
</Project>
11+
</Folder>
612
<Folder Name="/UniGetUI.Core.Logger/">
713
<Project Path="UniGetUI.Core.Logger/UniGetUI.Core.Logging.csproj">
814
<Platform Solution="*|arm64" Project="arm64" />

src/UniGetUI.Avalonia/App.axaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Application xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
x:Class="UniGetUI.Avalonia.App"
4+
RequestedThemeVariant="Default">
5+
<Application.Styles>
6+
<StyleInclude Source="avares://UniGetUI.Avalonia/Styles/AppStyles.axaml" />
7+
</Application.Styles>
8+
</Application>

src/UniGetUI.Avalonia/App.axaml.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System.ComponentModel;
2+
using Avalonia;
3+
using Avalonia.Controls.ApplicationLifetimes;
4+
using Avalonia.Markup.Xaml;
5+
using Avalonia.Styling;
6+
using Avalonia.Themes.Fluent;
7+
using Devolutions.AvaloniaTheme.DevExpress;
8+
using Devolutions.AvaloniaTheme.Linux;
9+
using Devolutions.AvaloniaTheme.MacOS;
10+
11+
namespace UniGetUI.Avalonia;
12+
13+
public partial class App : Application
14+
{
15+
public override void Initialize()
16+
{
17+
AvaloniaXamlLoader.Load(this);
18+
19+
Styles.Insert(0, CreatePlatformTheme());
20+
21+
Name = "UniGetUI.Avalonia";
22+
}
23+
24+
private static Styles CreatePlatformTheme()
25+
{
26+
Styles styles = OperatingSystem.IsWindows()
27+
? new DevolutionsDevExpressTheme()
28+
: OperatingSystem.IsMacOS()
29+
? new DevolutionsMacOsTheme()
30+
: OperatingSystem.IsLinux()
31+
? new DevolutionsLinuxYaruTheme()
32+
: new FluentTheme();
33+
34+
if (styles is ISupportInitialize initializable)
35+
{
36+
initializable.BeginInit();
37+
initializable.EndInit();
38+
}
39+
40+
return styles;
41+
}
42+
43+
public override void OnFrameworkInitializationCompleted()
44+
{
45+
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
46+
{
47+
desktop.MainWindow = new MainWindow();
48+
}
49+
50+
base.OnFrameworkInitializationCompleted();
51+
}
52+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System.Windows.Input;
2+
3+
namespace UniGetUI.Avalonia.Infrastructure;
4+
5+
internal sealed class AsyncCommand : ICommand
6+
{
7+
private readonly Func<Task> _executeAsync;
8+
private readonly Func<bool>? _canExecute;
9+
private bool _isExecuting;
10+
11+
public AsyncCommand(Func<Task> executeAsync, Func<bool>? canExecute = null)
12+
{
13+
_executeAsync = executeAsync;
14+
_canExecute = canExecute;
15+
}
16+
17+
public event EventHandler? CanExecuteChanged;
18+
19+
public bool CanExecute(object? parameter)
20+
{
21+
return !_isExecuting && (_canExecute?.Invoke() ?? true);
22+
}
23+
24+
public async void Execute(object? parameter)
25+
{
26+
if (!CanExecute(parameter))
27+
{
28+
return;
29+
}
30+
31+
_isExecuting = true;
32+
RaiseCanExecuteChanged();
33+
34+
try
35+
{
36+
await _executeAsync();
37+
}
38+
finally
39+
{
40+
_isExecuting = false;
41+
RaiseCanExecuteChanged();
42+
}
43+
}
44+
45+
public void RaiseCanExecuteChanged()
46+
{
47+
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
48+
}
49+
}

0 commit comments

Comments
 (0)