Non working start to a NavigationStack.

This commit is contained in:
2026-02-01 20:07:06 +01:00
parent e4dd1b6150
commit ed5f941ba8
13 changed files with 145 additions and 20 deletions

View File

@@ -17,6 +17,10 @@
<PackageVersion Include="Avalonia.Android" Version="11.3.10" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" />
<PackageVersion Include="ReactiveUI.SourceGenerators" Version="2.6.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageVersion>
<PackageVersion Include="Refit" Version="9.0.2" />
<PackageVersion Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1.15" />
</ItemGroup>

View File

@@ -3,13 +3,14 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core.Plugins;
using System.Linq;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Gamenight.Ui.ViewModels;
using Gamenight.Ui.Views;
using Microsoft.Extensions.DependencyInjection;
using Gamenight.Ui.Services;
using System;
using Refit;
using Gamenight.Ui.Models;
using ReactiveUI;
namespace Gamenight.Ui;
@@ -26,13 +27,21 @@ public partial class App : Application
{
// Register all the services needed for the application to run
var collection = new ServiceCollection();
var state = new GamenightState();
collection.AddSingleton(state);
var gamenightApi = RestService.For<IGamenightApi>("http://localhost:8080",
new RefitSettings {}
new RefitSettings
{
AuthorizationHeaderValueGetter = state.GetBearerToken
}
);
collection.AddSingleton(gamenightApi);
collection.AddSingleton<MainViewModel>();
collection.AddSingleton<IScreen>(sp => sp.GetRequiredService<MainViewModel>());
collection.AddSingleton<HeaderViewModel>();
collection.AddSingleton<SideBarViewModel>();
collection.AddSingleton<GamenightsViewModel>();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime)
{
collection.AddSingleton<MainWindow>();
@@ -45,7 +54,6 @@ public partial class App : Application
ServiceProvider = collection.BuildServiceProvider();
var mainViewModel = ServiceProvider.GetRequiredService<MainViewModel>();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
// Avoid duplicate validations from both Avalonia and the CommunityToolkit.

View File

@@ -22,6 +22,10 @@
</PackageReference>
<PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="ReactiveUI.SourceGenerators">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Refit" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,13 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Gamenight.Ui.Models;
public class GamenightState
{
public string JwtToken { get; set; } = "";
public Task<string> GetBearerToken(HttpRequestMessage req, CancellationToken ct) => Task.FromResult($"Bearer: {JwtToken}");
}

View File

@@ -3,6 +3,8 @@ using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Gamenight.Ui.ViewModels;
using Gamenight.Ui.Views;
using ReactiveUI;
namespace Gamenight.Ui;
@@ -12,7 +14,7 @@ namespace Gamenight.Ui;
[RequiresUnreferencedCode(
"Default implementation of ViewLocator involves reflection which may be trimmed away.",
Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")]
public class ViewLocator : IDataTemplate
public class ViewLocator : IDataTemplate, ReactiveUI.IViewLocator
{
public Control? Build(object? param)
{
@@ -34,4 +36,10 @@ public class ViewLocator : IDataTemplate
{
return data is ViewModelBase;
}
public IViewFor ResolveView<T>(T? viewModel, string? contract = null) => viewModel switch
{
GamenightsViewModel context => new GamenightsView { DataContext = context },
_ => throw new ArgumentOutOfRangeException(nameof(viewModel))
};
}

View File

@@ -0,0 +1,24 @@
using System;
using Gamenight.Ui.Services;
using ReactiveUI;
namespace Gamenight.Ui.ViewModels;
public class GamenightsViewModel : ReactiveObject, IRoutableViewModel
{
private IGamenightApi GamenightApi { get; }
public IScreen HostScreen { get; }
public string? UrlPathSegment { get; } = Guid.NewGuid().ToString().Substring(0, 5);
public GamenightsViewModel(IGamenightApi gamenightApi, IScreen hostScreen)
{
GamenightApi = gamenightApi;
HostScreen = hostScreen;
}
public GamenightsViewModel()
{
throw new NotImplementedException();
}
}

View File

@@ -1,33 +1,34 @@
using System.Threading.Tasks;
using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using Gamenight.Ui.Services;
using ReactiveUI;
using ReactiveUI.SourceGenerators;
namespace Gamenight.Ui.ViewModels;
public partial class MainViewModel : ViewModelBase
public partial class MainViewModel : ReactiveObject, IScreen
{
[ObservableProperty]
[ObservableAsProperty]
private string _greeting = "Welcome to Avalonia!";
private IGamenightApi GamenightApi { get; }
public HeaderViewModel HeaderViewModel { get; }
public SideBarViewModel SideBarViewModel { get; }
[ObservableProperty]
private ICommand _loginCommand;
public RoutingState Router { get; } = new RoutingState();
[ObservableAsProperty]
private IReactiveCommand _loginCommand;
public MainViewModel(IGamenightApi gamenightApi, HeaderViewModel headerViewModel, SideBarViewModel sideBarViewModel)
{
GamenightApi = gamenightApi;
HeaderViewModel = headerViewModel;
SideBarViewModel = sideBarViewModel;
SideBarViewModel.Screen = this;
_loginCommand = ReactiveCommand.Create(Test);
}
public MainViewModel() => throw new System.NotImplementedException();
public async Task Test() {
var token = await GamenightApi.Token(new Login() {
Username = "admin",

View File

@@ -1,7 +1,29 @@
using System.Reactive;
using ReactiveUI;
namespace Gamenight.Ui.ViewModels;
public class SideBarViewModel : ViewModelBase
public partial class SideBarViewModel : ReactiveObject
{
public IReactiveCommand<IRoutableViewModel, IRoutableViewModel> PushViewModel
{
get;
set => this.RaiseAndSetIfChanged(ref field, value);
}
public GamenightsViewModel GamenightsViewModel
{
get;
set => this.RaiseAndSetIfChanged(ref field, value);
}
public IScreen Screen { get; set; }
public SideBarViewModel(GamenightsViewModel gamenightsViewModel)
{
PushViewModel = ReactiveCommand.CreateFromObservable((IRoutableViewModel x) => Screen.Router.Navigate.Execute(x));
GamenightsViewModel = gamenightsViewModel;
}
}

View File

@@ -0,0 +1,14 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:Gamenight.Ui.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Gamenight.Ui.Views.GamenightsView"
x:DataType="vm:GamenightsViewModel">
<Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<vm:GamenightsViewModel />
</Design.DataContext>
</UserControl>

View File

@@ -0,0 +1,18 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Gamenight.Ui.ViewModels;
namespace Gamenight.Ui.Views;
public partial class GamenightsView : ReactiveUserControl<GamenightsViewModel>
{
public GamenightsView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@@ -3,7 +3,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:Gamenight.Ui.ViewModels"
xmlns:views="clr-namespace:Gamenight.Ui.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Gamenight.Ui.Views.HeaderView"
x:DataType="vm:HeaderViewModel">

View File

@@ -4,6 +4,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:Gamenight.Ui.ViewModels"
xmlns:views="clr-namespace:Gamenight.Ui.Views"
xmlns:reactiveUi="http://reactiveui.net"
xmlns:ui="clr-namespace:Gamenight.Ui"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Gamenight.Ui.Views.MainView"
x:DataType="vm:MainViewModel">
@@ -24,10 +26,17 @@
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<views:SideBarView Grid.Column="0" DataContext="{Binding SideBarViewModel}"/>
<StackPanel Grid.Column="1">
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center" />
<Button Content="Login" Command="{Binding LoginCommand}"/>
</StackPanel>
<reactiveUi:RoutedViewHost Grid.Column="1" Router="{Binding Router}">
<reactiveUi:RoutedViewHost.DefaultContent>
<TextBlock Text="Default content"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</reactiveUi:RoutedViewHost.DefaultContent>
<reactiveUi:RoutedViewHost.ViewLocator>
<!-- See AppViewLocator.cs section below -->
<ui:ViewLocator />
</reactiveUi:RoutedViewHost.ViewLocator>
</reactiveUi:RoutedViewHost>
</Grid>
</Grid>
</UserControl>

View File

@@ -12,4 +12,5 @@
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<vm:SideBarViewModel />
</Design.DataContext>
<Button Content="Gamenights" Command="{Binding PushViewModel}" CommandParameter="{Binding GamenightsViewModel}"/>
</UserControl>