Skip to content
This repository was archived by the owner on Apr 8, 2026. It is now read-only.
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
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ We welcome feedback, suggestions, and contributions from anyone who is intereste
To use AvaloniaInside.Shell in your Avalonia project, you can install the package via NuGet using the following command in the Package Manager Console:

```bash
dotnet add package AvaloniaInside.Shell --version 1.3.2.2
dotnet add package AvaloniaInside.Shell --version 1.3.3
```

Alternatively, you can also install the package through Visual Studio's NuGet Package Manager.
Expand All @@ -32,7 +32,6 @@ public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace()
.UseReactiveUI()
.UseShell();
```

Expand Down
35 changes: 0 additions & 35 deletions src/AvaloniaInside.Shell.sln
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Files", "Files", "{50F7051E
Directory.Packages.props = Directory.Packages.props
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShellBottomCustomNavigator", "Example\ShellBottomCustomNavigator\ShellBottomCustomNavigator\ShellBottomCustomNavigator.csproj", "{6D8FCCDB-C66B-4B81-9555-725E1EE912B8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShellBottomCustomNavigator.Android", "Example\ShellBottomCustomNavigator\ShellBottomCustomNavigator.Android\ShellBottomCustomNavigator.Android.csproj", "{4CEAC589-3446-4370-9357-C362E03DB56E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShellBottomCustomNavigator.Desktop", "Example\ShellBottomCustomNavigator\ShellBottomCustomNavigator.Desktop\ShellBottomCustomNavigator.Desktop.csproj", "{6FAADB02-7185-463E-8C93-5DAEE90EF782}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShellBottomCustomNavigator.iOS", "Example\ShellBottomCustomNavigator\ShellBottomCustomNavigator.iOS\ShellBottomCustomNavigator.iOS.csproj", "{C77D28D8-7583-40FE-9EA7-6E8B5F902A8B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShellBottomCustomNavigator.Browser", "Example\ShellBottomCustomNavigator\ShellBottomCustomNavigator.Browser\ShellBottomCustomNavigator.Browser.csproj", "{58D15C4F-ADC5-4550-9224-365FFB38955D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -66,26 +56,6 @@ Global
{75883514-564B-4EBA-83D8-68FD9FDB0CB2}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{75883514-564B-4EBA-83D8-68FD9FDB0CB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75883514-564B-4EBA-83D8-68FD9FDB0CB2}.Release|Any CPU.Build.0 = Release|Any CPU
{6D8FCCDB-C66B-4B81-9555-725E1EE912B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6D8FCCDB-C66B-4B81-9555-725E1EE912B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6D8FCCDB-C66B-4B81-9555-725E1EE912B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6D8FCCDB-C66B-4B81-9555-725E1EE912B8}.Release|Any CPU.Build.0 = Release|Any CPU
{4CEAC589-3446-4370-9357-C362E03DB56E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4CEAC589-3446-4370-9357-C362E03DB56E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4CEAC589-3446-4370-9357-C362E03DB56E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4CEAC589-3446-4370-9357-C362E03DB56E}.Release|Any CPU.Build.0 = Release|Any CPU
{6FAADB02-7185-463E-8C93-5DAEE90EF782}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6FAADB02-7185-463E-8C93-5DAEE90EF782}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6FAADB02-7185-463E-8C93-5DAEE90EF782}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6FAADB02-7185-463E-8C93-5DAEE90EF782}.Release|Any CPU.Build.0 = Release|Any CPU
{C77D28D8-7583-40FE-9EA7-6E8B5F902A8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C77D28D8-7583-40FE-9EA7-6E8B5F902A8B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C77D28D8-7583-40FE-9EA7-6E8B5F902A8B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C77D28D8-7583-40FE-9EA7-6E8B5F902A8B}.Release|Any CPU.Build.0 = Release|Any CPU
{58D15C4F-ADC5-4550-9224-365FFB38955D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{58D15C4F-ADC5-4550-9224-365FFB38955D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{58D15C4F-ADC5-4550-9224-365FFB38955D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{58D15C4F-ADC5-4550-9224-365FFB38955D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -96,11 +66,6 @@ Global
{721FC232-3B13-492D-A96B-C6784FEDB10C} = {A495BEFD-2260-489E-A8F3-003EF9603D43}
{E2E23F24-CC66-4940-91EA-68C28DC04602} = {A495BEFD-2260-489E-A8F3-003EF9603D43}
{75883514-564B-4EBA-83D8-68FD9FDB0CB2} = {A495BEFD-2260-489E-A8F3-003EF9603D43}
{6D8FCCDB-C66B-4B81-9555-725E1EE912B8} = {A495BEFD-2260-489E-A8F3-003EF9603D43}
{4CEAC589-3446-4370-9357-C362E03DB56E} = {A495BEFD-2260-489E-A8F3-003EF9603D43}
{6FAADB02-7185-463E-8C93-5DAEE90EF782} = {A495BEFD-2260-489E-A8F3-003EF9603D43}
{C77D28D8-7583-40FE-9EA7-6E8B5F902A8B} = {A495BEFD-2260-489E-A8F3-003EF9603D43}
{58D15C4F-ADC5-4550-9224-365FFB38955D} = {A495BEFD-2260-489E-A8F3-003EF9603D43}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {10F59C6F-508A-4C41-820B-AAC163C9FDB2}
Expand Down
50 changes: 26 additions & 24 deletions src/AvaloniaInside.Shell/AppBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,43 +1,45 @@
using System;
using Avalonia;
using AvaloniaInside.Shell.Presenters;
using Splat;

namespace AvaloniaInside.Shell;

public static class AppBuilderExtensions
{
public static AppBuilder UseShell(this AppBuilder builder, Func<INavigationViewLocator>? viewLocatorFactory = null) =>
builder.AfterPlatformServicesSetup(_ => Locator.RegisterResolverCallbackChanged(() =>
builder.AfterPlatformServicesSetup(_ =>
{
if (Locator.CurrentMutable is null)
{
return;
}

Locator.CurrentMutable.Register<INavigationRegistrar, NavigationRegistrar>();
Locator.CurrentMutable.Register<IPresenterProvider, PresenterProvider>();
AvaloniaLocator.CurrentMutable
.Bind<INavigationRegistrar>().ToSingleton<NavigationRegistrar>()
.Bind<IPresenterProvider>().ToSingleton<PresenterProvider>();

if (viewLocatorFactory is null)
{
Locator.CurrentMutable.Register<INavigationViewLocator, DefaultNavigationViewLocator>();
AvaloniaLocator.CurrentMutable
.Bind<INavigationViewLocator>().ToSingleton<DefaultNavigationViewLocator>();
}

Locator.CurrentMutable.Register<INavigationUpdateStrategy>(() =>
new DefaultNavigationUpdateStrategy(Locator.Current.GetService<IPresenterProvider>()!));

Locator.CurrentMutable.Register<INavigator>(() =>
{
var viewLocator = viewLocatorFactory != null ? viewLocatorFactory.Invoke() : Locator.Current.GetService<INavigationViewLocator>()!;
var registrar = Locator.Current.GetService<INavigationRegistrar>()!;
return new Navigator(
registrar,
new RelativeNavigateStrategy(registrar),
Locator.Current.GetService<INavigationUpdateStrategy>()!,
viewLocator
AvaloniaLocator.CurrentMutable
.Bind<INavigationUpdateStrategy>()
.ToFunc(() =>
new DefaultNavigationUpdateStrategy(
AvaloniaLocator.CurrentMutable.GetService<IPresenterProvider>()!));


AvaloniaLocator.CurrentMutable
.Bind<INavigator>()
.ToFunc(() =>
{
var viewLocator = viewLocatorFactory != null ? viewLocatorFactory.Invoke() : AvaloniaLocator.CurrentMutable.GetService<INavigationViewLocator>()!;
var registrar = AvaloniaLocator.CurrentMutable.GetService<INavigationRegistrar>()!;
return new Navigator(
registrar,
new RelativeNavigateStrategy(registrar),
AvaloniaLocator.CurrentMutable.GetService<INavigationUpdateStrategy>()!,
viewLocator
);
});
}));
});
});

public static AppBuilder UseShell(this AppBuilder builder, Func<NavigationNode, object> viewFactory)
=> builder.UseShell(() => new DelegateNavigationViewLocator(viewFactory));
Expand Down
7 changes: 4 additions & 3 deletions src/AvaloniaInside.Shell/AvaloniaInside.Shell.csproj
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<Version>1.3.2.2</Version>
<Version>1.3.3</Version>
<Title>Shell view for Avalonia</Title>
<Description>Shell reduces the complexity of mobile/desktop application development by providing the fundamental features that most applications require</Description>
<Copyright>AvaloniaInside</Copyright>
Expand All @@ -13,6 +13,8 @@
<PackageTags>avalonia,shell</PackageTags>
<PackageReadmeFile>README.md</PackageReadmeFile>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AvaloniaAccessUnstablePrivateApis>true</AvaloniaAccessUnstablePrivateApis>
<Avalonia_I_Want_To_Use_Private_Apis_In_Nuget_Package_And_Promise_To_Pin_The_Exact_Avalonia_Version_In_Package_Dependency>true</Avalonia_I_Want_To_Use_Private_Apis_In_Nuget_Package_And_Promise_To_Pin_The_Exact_Avalonia_Version_In_Package_Dependency>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
Expand All @@ -24,7 +26,6 @@
<ItemGroup>
<PackageReference Include="Avalonia" />
<PackageReference Include="Avalonia.Themes.Fluent" />
<PackageReference Include="Avalonia.ReactiveUI" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" />
</ItemGroup>
Expand Down
40 changes: 19 additions & 21 deletions src/AvaloniaInside.Shell/Navigator.Attach.cs
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
using Avalonia;
using Avalonia.Input;
using System;

namespace AvaloniaInside.Shell;

public partial class Navigator
{
static Navigator()
{
ToProperty.Changed.Subscribe(HandleToChanged);
}

#region To Property

public static readonly AttachedProperty<BindingNavigate> ToProperty =
AvaloniaProperty.RegisterAttached<Navigator, AvaloniaObject, BindingNavigate>("To");
AvaloniaProperty.RegisterAttached<Navigator, AvaloniaObject, BindingNavigate>(
"To",
coerce: HandleToChanged);

private static void HandleToChanged(AvaloniaPropertyChangedEventArgs<BindingNavigate> e)
private static BindingNavigate HandleToChanged(AvaloniaObject obj, BindingNavigate value)
{
try
{
if (e.Sender is ICommandSource commandSource)
{
if (e.NewValue is { HasValue: true } v)
{
((dynamic)commandSource).Command = v.Value;
v.Value.Sender = e.Sender;
}
}
}
catch { /*IGNORE*/ }
}
try
{
if (obj is ICommandSource commandSource)
{
((dynamic)commandSource).Command = value;
value.Sender = obj;
}
}
catch
{
/*IGNORE*/
}

return value;
}

public static BindingNavigate GetTo(AvaloniaObject element) =>
element.GetValue(ToProperty);
Expand Down
8 changes: 3 additions & 5 deletions src/AvaloniaInside.Shell/ShellView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
using Avalonia.Input;
using Avalonia.Interactivity;
using AvaloniaInside.Shell.Platform;
using ReactiveUI;
using Splat;

namespace AvaloniaInside.Shell;

Expand Down Expand Up @@ -338,12 +336,12 @@

public ShellView()
{
Navigator = Locator.Current
Navigator = AvaloniaLocator.CurrentMutable
.GetService<INavigator>() ?? throw new ArgumentException("Cannot find INavigationService");
Navigator.RegisterShell(this);

BackCommand = ReactiveCommand.CreateFromTask(BackActionAsync);
SideMenuCommand = ReactiveCommand.CreateFromTask(MenuActionAsync);
BackCommand = new SimpleAsyncCommand(BackActionAsync);
SideMenuCommand = new SimpleAsyncCommand(MenuActionAsync);

var isMobile = OperatingSystem.IsAndroid() || OperatingSystem.IsIOS();
if (!isMobile)
Expand Down Expand Up @@ -375,7 +373,7 @@

if (EnableSafeArea && TopLevel.GetTopLevel(this) is { InsetsManager: { } insetsManager })
{
insetsManager.DisplayEdgeToEdge = true;

Check warning on line 376 in src/AvaloniaInside.Shell/ShellView.cs

View workflow job for this annotation

GitHub Actions / build

'IInsetsManager.DisplayEdgeToEdge' is obsolete: 'Use DisplayEdgeToEdgePreference'
insetsManager.SafeAreaChanged += (s, e) => OnSafeEdgeSetup();
}

Expand Down Expand Up @@ -434,7 +432,7 @@

TopLevel.SetAutoSafeAreaPadding(this, false);

if (TopLevel.GetTopLevel(this) is { InsetsManager: { DisplayEdgeToEdge: true } insetsManager })

Check warning on line 435 in src/AvaloniaInside.Shell/ShellView.cs

View workflow job for this annotation

GitHub Actions / build

'IInsetsManager.DisplayEdgeToEdge' is obsolete: 'Use DisplayEdgeToEdgePreference'
SafePadding = insetsManager.SafeAreaPadding;
}

Expand Down
62 changes: 62 additions & 0 deletions src/AvaloniaInside.Shell/SimpleAsyncCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;

namespace AvaloniaInside.Shell;

public class SimpleAsyncCommand : ICommand
{
private readonly Func<object?, CancellationToken, Task> _execute;
private readonly Func<object?, bool>? _canExecute;
private bool _isExecuting;

public SimpleAsyncCommand(Func<Task> execute, Func<bool>? canExecute = null)
: this((_, _) => execute(), canExecute == null ? null : _ => canExecute())
{
}

public SimpleAsyncCommand(Func<object?, Task> execute, Func<object?, bool>? canExecute = null)
: this((param, _) => execute(param), canExecute)
{
}

public SimpleAsyncCommand(Func<CancellationToken, Task> execute, Func<bool>? canExecute = null)
: this((_, ct) => execute(ct), canExecute == null ? null : _ => canExecute())
{
}

public SimpleAsyncCommand(Func<object?, CancellationToken, Task> execute, Func<object?, bool>? canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}

public bool CanExecute(object? parameter) => !_isExecuting && (_canExecute?.Invoke(parameter) ?? true);

public void Execute(object? parameter)
{
// Fire-and-forget from ICommand.Execute (void). Use a safe, observed task.
_ = ExecuteAsync(parameter, CancellationToken.None);
}

public event EventHandler? CanExecuteChanged;

public async Task ExecuteAsync(object? parameter, CancellationToken cancellationToken)
{
if (_isExecuting) return;

try
{
_isExecuting = true;
CanExecuteChanged?.Invoke(this, EventArgs.Empty);

await _execute(parameter, cancellationToken).ConfigureAwait(false);
}
finally
{
_isExecuting = false;
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<Project>
<PropertyGroup>
<AvaloniaVersion>11.2.5</AvaloniaVersion>
<AvaloniaVersion>11.3.12</AvaloniaVersion>
</PropertyGroup>
</Project>
40 changes: 19 additions & 21 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
<Project>
<PropertyGroup>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="11.3.9"/>
<PackageVersion Include="Avalonia.Desktop" Version="11.3.9" />
<PackageVersion Include="Avalonia.Android" Version="11.3.9" />
<PackageVersion Include="Avalonia.iOS" Version="11.3.9" />
<PackageVersion Include="Avalonia.Browser" Version="11.3.9" />
<PackageVersion Include="Avalonia.Themes.Fluent" Version="11.3.9" />
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.3.9" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.9" />
<PackageVersion Include="Avalonia.Fonts.Inter" Version="11.3.9" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="Projektanker.Icons.Avalonia" Version="9.6.2" />
<PackageVersion Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.6.2" />
<PackageVersion Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1.17" />
<PackageVersion Include="ReactiveUI" Version="21.0.1" />
</ItemGroup>
</Project>
<PropertyGroup>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="11.3.12" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.12" />
<PackageVersion Include="Avalonia.Android" Version="11.3.12" />
<PackageVersion Include="Avalonia.iOS" Version="11.3.12" />
<PackageVersion Include="Avalonia.Browser" Version="11.3.12" />
<PackageVersion Include="Avalonia.Themes.Fluent" Version="11.3.12" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.12" />
<PackageVersion Include="Avalonia.Fonts.Inter" Version="11.3.12" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="Projektanker.Icons.Avalonia" Version="9.6.2" />
<PackageVersion Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.6.2" />
<PackageVersion Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1.17" />
</ItemGroup>
</Project>
Loading
Loading