From 42fde38db66abb988671fb2fc41335dc1216318f Mon Sep 17 00:00:00 2001 From: Cinka Date: Sat, 21 Dec 2024 13:11:30 +0300 Subject: [PATCH] - tweak: Dependency injection think --- .idea/.idea.Nebula/.idea/.name | 1 + .idea/.idea.Nebula/.idea/avalonia.xml | 2 + Nebula.Launcher/App.axaml | 68 +++++++++------- Nebula.Launcher/App.axaml.cs | 20 ++++- Nebula.Launcher/Converters/TypeConverters.cs | 21 +++++ Nebula.Launcher/Models/ListItemTemplate.cs | 4 + Nebula.Launcher/Nebula.Launcher.csproj | 3 +- Nebula.Launcher/Program.cs | 2 - .../ServiceCollectionExtensions.cs | 52 +++++++++++++ .../ViewModels/AccountInfoViewModel.cs | 8 ++ Nebula.Launcher/ViewModels/MainViewModel.cs | 63 +++++++++++++++ Nebula.Launcher/ViewModels/TestViewModel.cs | 21 ----- Nebula.Launcher/ViewModels/ViewModelBase.cs | 5 +- .../Controls/PlayerContainerControl.axaml | 45 +++++++---- .../Controls/ServerContainerControl.axaml | 77 ++++++++++++------- Nebula.Launcher/Views/MainView.axaml | 70 +++++++++++++++++ Nebula.Launcher/Views/MainView.axaml.cs | 25 ++++++ Nebula.Launcher/Views/MainWindow.axaml | 69 ++++------------- Nebula.Launcher/Views/MainWindow.axaml.cs | 8 ++ .../Views/Pages/AccountInfoPage.axaml | 59 ++++++++++++++ .../Views/Pages/AccountInfoPage.axaml.cs | 19 +++++ .../Views/Pages/ServerListPage.axaml | 41 ++++++++++ .../Views/Pages/ServerListPage.axaml.cs | 11 +++ .../Views/Tabs/AccountInfoTab.axaml | 43 ----------- .../Views/Tabs/AccountInfoTab.axaml.cs | 13 ---- .../Views/Tabs/ServerListTab.axaml | 22 ------ .../Views/Tabs/ServerListTab.axaml.cs | 11 --- 27 files changed, 541 insertions(+), 242 deletions(-) create mode 100644 .idea/.idea.Nebula/.idea/.name create mode 100644 Nebula.Launcher/Converters/TypeConverters.cs create mode 100644 Nebula.Launcher/Models/ListItemTemplate.cs create mode 100644 Nebula.Launcher/ServiceCollectionExtensions.cs create mode 100644 Nebula.Launcher/ViewModels/AccountInfoViewModel.cs create mode 100644 Nebula.Launcher/ViewModels/MainViewModel.cs delete mode 100644 Nebula.Launcher/ViewModels/TestViewModel.cs create mode 100644 Nebula.Launcher/Views/MainView.axaml create mode 100644 Nebula.Launcher/Views/MainView.axaml.cs create mode 100644 Nebula.Launcher/Views/Pages/AccountInfoPage.axaml create mode 100644 Nebula.Launcher/Views/Pages/AccountInfoPage.axaml.cs create mode 100644 Nebula.Launcher/Views/Pages/ServerListPage.axaml create mode 100644 Nebula.Launcher/Views/Pages/ServerListPage.axaml.cs delete mode 100644 Nebula.Launcher/Views/Tabs/AccountInfoTab.axaml delete mode 100644 Nebula.Launcher/Views/Tabs/AccountInfoTab.axaml.cs delete mode 100644 Nebula.Launcher/Views/Tabs/ServerListTab.axaml delete mode 100644 Nebula.Launcher/Views/Tabs/ServerListTab.axaml.cs diff --git a/.idea/.idea.Nebula/.idea/.name b/.idea/.idea.Nebula/.idea/.name new file mode 100644 index 0000000..c622c56 --- /dev/null +++ b/.idea/.idea.Nebula/.idea/.name @@ -0,0 +1 @@ +Nebula \ No newline at end of file diff --git a/.idea/.idea.Nebula/.idea/avalonia.xml b/.idea/.idea.Nebula/.idea/avalonia.xml index 7586366..8ae03a6 100644 --- a/.idea/.idea.Nebula/.idea/avalonia.xml +++ b/.idea/.idea.Nebula/.idea/avalonia.xml @@ -7,7 +7,9 @@ + + diff --git a/Nebula.Launcher/App.axaml b/Nebula.Launcher/App.axaml index ba40e95..d65d93c 100644 --- a/Nebula.Launcher/App.axaml +++ b/Nebula.Launcher/App.axaml @@ -1,46 +1,60 @@ - - + - + - + + - - + - + - + + + + + + \ No newline at end of file diff --git a/Nebula.Launcher/App.axaml.cs b/Nebula.Launcher/App.axaml.cs index be91370..0d7c32c 100644 --- a/Nebula.Launcher/App.axaml.cs +++ b/Nebula.Launcher/App.axaml.cs @@ -1,9 +1,11 @@ +using System; using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Data.Core; using Avalonia.Data.Core.Plugins; using System.Linq; using Avalonia.Markup.Xaml; +using Microsoft.Extensions.DependencyInjection; using Nebula.Launcher.ViewModels; using Nebula.Launcher.Views; @@ -11,6 +13,8 @@ namespace Nebula.Launcher; public partial class App : Application { + private IServiceProvider _serviceProvider = null!; + public override void Initialize() { AvaloniaXamlLoader.Load(this); @@ -18,10 +22,20 @@ public partial class App : Application public override void OnFrameworkInitializationCompleted() { - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + var services = new ServiceCollection(); + services.AddServices(); + + _serviceProvider = services.BuildServiceProvider(); + + switch (ApplicationLifetime) { - DisableAvaloniaDataAnnotationValidation(); - desktop.MainWindow = new MainWindow(); + case IClassicDesktopStyleApplicationLifetime desktop: + DisableAvaloniaDataAnnotationValidation(); + desktop.MainWindow = _serviceProvider.GetService(); + break; + case ISingleViewApplicationLifetime singleViewPlatform: + singleViewPlatform.MainView = _serviceProvider.GetRequiredService(); + break; } base.OnFrameworkInitializationCompleted(); diff --git a/Nebula.Launcher/Converters/TypeConverters.cs b/Nebula.Launcher/Converters/TypeConverters.cs new file mode 100644 index 0000000..61909a2 --- /dev/null +++ b/Nebula.Launcher/Converters/TypeConverters.cs @@ -0,0 +1,21 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Data.Converters; +using Avalonia.Media; + +namespace Nebula.Launcher.Converters; + +public class TypeConverters +{ + private const string StreamGeometryNotFound = + "M24 4C35.0457 4 44 12.9543 44 24C44 35.0457 35.0457 44 24 44C12.9543 44 4 35.0457 4 24C4 12.9543 12.9543 4 24 4ZM24 6.5C14.335 6.5 6.5 14.335 6.5 24C6.5 33.665 14.335 41.5 24 41.5C33.665 41.5 41.5 33.665 41.5 24C41.5 14.335 33.665 6.5 24 6.5ZM24.25 32C25.0784 32 25.75 32.6716 25.75 33.5C25.75 34.3284 25.0784 35 24.25 35C23.4216 35 22.75 34.3284 22.75 33.5C22.75 32.6716 23.4216 32 24.25 32ZM24.25 13C27.6147 13 30.5 15.8821 30.5 19.2488C30.502 21.3691 29.7314 22.7192 27.8216 24.7772L26.8066 25.8638C25.7842 27.0028 25.3794 27.7252 25.3409 28.5793L25.3379 28.7411L25.3323 28.8689L25.3143 28.9932C25.2018 29.5636 24.7009 29.9957 24.0968 30.0001C23.4065 30.0049 22.8428 29.4493 22.8379 28.7589C22.8251 26.9703 23.5147 25.7467 25.1461 23.9739L26.1734 22.8762C27.5312 21.3837 28.0012 20.503 28 19.25C28 17.2634 26.2346 15.5 24.25 15.5C22.3307 15.5 20.6142 17.1536 20.5055 19.0587L20.4935 19.3778C20.4295 20.0081 19.8972 20.5 19.25 20.5C18.5596 20.5 18 19.9404 18 19.25C18 15.8846 20.8864 13 24.25 13Z"; + + public static FuncValueConverter IconConverter { get; } = + new(iconKey => + { + if (iconKey is null) return StreamGeometry.Parse(StreamGeometryNotFound); + + Application.Current!.TryFindResource(iconKey, out var resource); + return resource as StreamGeometry ?? StreamGeometry.Parse(StreamGeometryNotFound); + }); +} \ No newline at end of file diff --git a/Nebula.Launcher/Models/ListItemTemplate.cs b/Nebula.Launcher/Models/ListItemTemplate.cs new file mode 100644 index 0000000..33d0d11 --- /dev/null +++ b/Nebula.Launcher/Models/ListItemTemplate.cs @@ -0,0 +1,4 @@ +using System; + +namespace Nebula.Launcher.Models; +public record ListItemTemplate(Type ModelType, string IconKey, string Label); \ No newline at end of file diff --git a/Nebula.Launcher/Nebula.Launcher.csproj b/Nebula.Launcher/Nebula.Launcher.csproj index a80e71f..895c2e6 100644 --- a/Nebula.Launcher/Nebula.Launcher.csproj +++ b/Nebula.Launcher/Nebula.Launcher.csproj @@ -9,7 +9,6 @@ - @@ -17,7 +16,6 @@ - @@ -26,6 +24,7 @@ All + diff --git a/Nebula.Launcher/Program.cs b/Nebula.Launcher/Program.cs index 5ca638c..b27b941 100644 --- a/Nebula.Launcher/Program.cs +++ b/Nebula.Launcher/Program.cs @@ -1,6 +1,5 @@ using Avalonia; using System; -using Avalonia.ReactiveUI; namespace Nebula.Launcher; @@ -18,6 +17,5 @@ sealed class Program => AppBuilder.Configure() .UsePlatformDetect() .WithInterFont() - .UseReactiveUI() .LogToTrace(); } \ No newline at end of file diff --git a/Nebula.Launcher/ServiceCollectionExtensions.cs b/Nebula.Launcher/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..8f7e5d7 --- /dev/null +++ b/Nebula.Launcher/ServiceCollectionExtensions.cs @@ -0,0 +1,52 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Threading; +using Microsoft.Extensions.DependencyInjection; +using Nebula.Launcher.ViewModels; +using Nebula.Launcher.Views; +using Nebula.Launcher.Views.Pages; + +namespace Nebula.Launcher; + +public static class ServiceCollectionExtensions +{ + public static void AddServices(this IServiceCollection services) + { + services.AddAvaloniaServices(); + services.AddViews(); + } + + private static void AddAvaloniaServices(this IServiceCollection services) + { + services.AddSingleton(_ => Dispatcher.UIThread); + services.AddSingleton(_ => Application.Current?.ApplicationLifetime ?? throw new InvalidOperationException("No application lifetime is set")); + + services.AddSingleton(sp => + sp.GetRequiredService() switch + { + IClassicDesktopStyleApplicationLifetime desktop => desktop.MainWindow ?? throw new InvalidOperationException("No main window set"), + ISingleViewApplicationLifetime singleViewPlatform => TopLevel.GetTopLevel(singleViewPlatform.MainView) ?? throw new InvalidOperationException("Could not find top level element for single view"), + _ => throw new InvalidOperationException($"Could not find {nameof(TopLevel)} element"), + } + ); + + services.AddSingleton(sp => sp.GetRequiredService().StorageProvider); + } + + private static void AddViews(this IServiceCollection services) + { + services.AddTransient(); + services.AddView(); + services.AddView(); + } + + private static void AddView(this IServiceCollection services) + where TView : class + where TViewModel : class + { + services.AddTransient(); + services.AddTransient(); + } +} \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/AccountInfoViewModel.cs b/Nebula.Launcher/ViewModels/AccountInfoViewModel.cs new file mode 100644 index 0000000..347fe8b --- /dev/null +++ b/Nebula.Launcher/ViewModels/AccountInfoViewModel.cs @@ -0,0 +1,8 @@ +using Nebula.Launcher.Views.Pages; + +namespace Nebula.Launcher.ViewModels; + +public class AccountInfoViewModel : ViewModelBase, ITab +{ + +} diff --git a/Nebula.Launcher/ViewModels/MainViewModel.cs b/Nebula.Launcher/ViewModels/MainViewModel.cs new file mode 100644 index 0000000..e01db28 --- /dev/null +++ b/Nebula.Launcher/ViewModels/MainViewModel.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Avalonia.Controls; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.DependencyInjection; +using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Messaging; +using Nebula.Launcher.Models; + +namespace Nebula.Launcher.ViewModels; + + +public partial class MainViewModel : ViewModelBase +{ + public MainViewModel(AccountInfoViewModel accountInfoViewModel, IServiceProvider serviceProvider) + { + _currentPage = accountInfoViewModel; + _serviceProvider = serviceProvider; + Items = new ObservableCollection(_templates); + + SelectedListItem = Items.First(vm => vm.ModelType == typeof(AccountInfoViewModel)); + } + + private readonly List _templates = + [ + new ListItemTemplate(typeof(AccountInfoViewModel), "HomeRegular", "Account"), + ]; + + [ObservableProperty] + private bool _isPaneOpen; + + [ObservableProperty] + private ViewModelBase _currentPage; + + private readonly IServiceProvider _serviceProvider; + + [ObservableProperty] + private ListItemTemplate? _selectedListItem; + + partial void OnSelectedListItemChanged(ListItemTemplate? value) + { + if (value is null) return; + + var vm = Design.IsDesignMode + ? Activator.CreateInstance(value.ModelType) + : _serviceProvider.GetService(value.ModelType); + + if (vm is not ViewModelBase vmb) return; + + CurrentPage = vmb; + } + + public ObservableCollection Items { get; } + + [RelayCommand] + private void TriggerPane() + { + IsPaneOpen = !IsPaneOpen; + } +} + diff --git a/Nebula.Launcher/ViewModels/TestViewModel.cs b/Nebula.Launcher/ViewModels/TestViewModel.cs deleted file mode 100644 index 9556325..0000000 --- a/Nebula.Launcher/ViewModels/TestViewModel.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using ReactiveUI; - -namespace Nebula.Launcher.ViewModels; - -public sealed class TestViewModel : ViewModelBase -{ - private string _greeting = "Welcome to Avalonia!"; - - public string Greeting - { - get => _greeting; - set => this.RaiseAndSetIfChanged(ref _greeting, value); - } - - public void ButtonAction() - { - Console.WriteLine("HAS"); - Greeting = "Another greeting from Avalonia"; - } -} \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/ViewModelBase.cs b/Nebula.Launcher/ViewModels/ViewModelBase.cs index 2dbe305..9590019 100644 --- a/Nebula.Launcher/ViewModels/ViewModelBase.cs +++ b/Nebula.Launcher/ViewModels/ViewModelBase.cs @@ -1,7 +1,8 @@ -using ReactiveUI; +using Avalonia.Controls; +using CommunityToolkit.Mvvm.ComponentModel; namespace Nebula.Launcher.ViewModels; -public abstract class ViewModelBase : ReactiveObject +public abstract class ViewModelBase : ObservableObject { } \ No newline at end of file diff --git a/Nebula.Launcher/Views/Controls/PlayerContainerControl.axaml b/Nebula.Launcher/Views/Controls/PlayerContainerControl.axaml index 6f00507..8c966d3 100644 --- a/Nebula.Launcher/Views/Controls/PlayerContainerControl.axaml +++ b/Nebula.Launcher/Views/Controls/PlayerContainerControl.axaml @@ -1,19 +1,36 @@ - - + + - - Name: - Ni Higgers + + + - - - + + + diff --git a/Nebula.Launcher/Views/Controls/ServerContainerControl.axaml b/Nebula.Launcher/Views/Controls/ServerContainerControl.axaml index efae3e4..73bae87 100644 --- a/Nebula.Launcher/Views/Controls/ServerContainerControl.axaml +++ b/Nebula.Launcher/Views/Controls/ServerContainerControl.axaml @@ -1,36 +1,57 @@ - - - - Server name + + + + - - 15/15 + + - - + + - - + - - - + + + + + diff --git a/Nebula.Launcher/Views/MainView.axaml b/Nebula.Launcher/Views/MainView.axaml new file mode 100644 index 0000000..cb1cef9 --- /dev/null +++ b/Nebula.Launcher/Views/MainView.axaml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Nebula.Launcher/Views/MainView.axaml.cs b/Nebula.Launcher/Views/MainView.axaml.cs new file mode 100644 index 0000000..b70c83b --- /dev/null +++ b/Nebula.Launcher/Views/MainView.axaml.cs @@ -0,0 +1,25 @@ +using System; +using System.Windows.Input; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Nebula.Launcher.ViewModels; + +namespace Nebula.Launcher.Views; + +public partial class MainView : UserControl +{ + + // This constructor is used when the view is created by the XAML Previewer + public MainView() + { + InitializeComponent(); + } + + // This constructor is used when the view is created via dependency injection + public MainView(MainViewModel viewModel) + : this() + { + DataContext = viewModel; + } +} \ No newline at end of file diff --git a/Nebula.Launcher/Views/MainWindow.axaml b/Nebula.Launcher/Views/MainWindow.axaml index 56bb703..9731dde 100644 --- a/Nebula.Launcher/Views/MainWindow.axaml +++ b/Nebula.Launcher/Views/MainWindow.axaml @@ -1,54 +1,15 @@ - - - - - - - - - - - - - - - - - - cinka.ru - v0.01 - - - - - + diff --git a/Nebula.Launcher/Views/MainWindow.axaml.cs b/Nebula.Launcher/Views/MainWindow.axaml.cs index 0c12e8f..e2a8032 100644 --- a/Nebula.Launcher/Views/MainWindow.axaml.cs +++ b/Nebula.Launcher/Views/MainWindow.axaml.cs @@ -4,8 +4,16 @@ namespace Nebula.Launcher.Views; public partial class MainWindow : Window { + // This constructor is used when the view is created by the XAML Previewer public MainWindow() { InitializeComponent(); } + + // This constructor is used when the view is created via dependency injection + public MainWindow(MainView mainView) + : this() + { + Content = mainView; + } } \ No newline at end of file diff --git a/Nebula.Launcher/Views/Pages/AccountInfoPage.axaml b/Nebula.Launcher/Views/Pages/AccountInfoPage.axaml new file mode 100644 index 0000000..c13ea3e --- /dev/null +++ b/Nebula.Launcher/Views/Pages/AccountInfoPage.axaml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Nebula.Launcher/Views/Pages/AccountInfoPage.axaml.cs b/Nebula.Launcher/Views/Pages/AccountInfoPage.axaml.cs new file mode 100644 index 0000000..8b6c074 --- /dev/null +++ b/Nebula.Launcher/Views/Pages/AccountInfoPage.axaml.cs @@ -0,0 +1,19 @@ +using Avalonia.Controls; +using Nebula.Launcher.ViewModels; + +namespace Nebula.Launcher.Views.Pages; + +public interface ITab; +public partial class AccountInfoPage : UserControl +{ + public AccountInfoPage() + { + InitializeComponent(); + } + + public AccountInfoPage(AccountInfoViewModel viewModel) + : this() + { + DataContext = viewModel; + } +} \ No newline at end of file diff --git a/Nebula.Launcher/Views/Pages/ServerListPage.axaml b/Nebula.Launcher/Views/Pages/ServerListPage.axaml new file mode 100644 index 0000000..b9cf856 --- /dev/null +++ b/Nebula.Launcher/Views/Pages/ServerListPage.axaml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Nebula.Launcher/Views/Pages/ServerListPage.axaml.cs b/Nebula.Launcher/Views/Pages/ServerListPage.axaml.cs new file mode 100644 index 0000000..3f9a3a5 --- /dev/null +++ b/Nebula.Launcher/Views/Pages/ServerListPage.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace Nebula.Launcher.Views.Pages; + +public partial class ServerListPage : UserControl +{ + public ServerListPage() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/Nebula.Launcher/Views/Tabs/AccountInfoTab.axaml b/Nebula.Launcher/Views/Tabs/AccountInfoTab.axaml deleted file mode 100644 index c516891..0000000 --- a/Nebula.Launcher/Views/Tabs/AccountInfoTab.axaml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - Login: - - Password: - - Auth server: - - - - - - - - - - - - Profiles: - - - - - - - - - - diff --git a/Nebula.Launcher/Views/Tabs/AccountInfoTab.axaml.cs b/Nebula.Launcher/Views/Tabs/AccountInfoTab.axaml.cs deleted file mode 100644 index 4c77a11..0000000 --- a/Nebula.Launcher/Views/Tabs/AccountInfoTab.axaml.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; - -namespace Nebula.Launcher.Views.Tabs; - -public partial class AccountInfoTab : UserControl -{ - public AccountInfoTab() - { - InitializeComponent(); - } -} \ No newline at end of file diff --git a/Nebula.Launcher/Views/Tabs/ServerListTab.axaml b/Nebula.Launcher/Views/Tabs/ServerListTab.axaml deleted file mode 100644 index f59edb0..0000000 --- a/Nebula.Launcher/Views/Tabs/ServerListTab.axaml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - Search... - - - - - diff --git a/Nebula.Launcher/Views/Tabs/ServerListTab.axaml.cs b/Nebula.Launcher/Views/Tabs/ServerListTab.axaml.cs deleted file mode 100644 index a744527..0000000 --- a/Nebula.Launcher/Views/Tabs/ServerListTab.axaml.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Avalonia.Controls; - -namespace Nebula.Launcher.Views.Tabs; - -public partial class ServerListTab : UserControl -{ - public ServerListTab() - { - InitializeComponent(); - } -} \ No newline at end of file