diff --git a/Needlework.Net/App.axaml.cs b/Needlework.Net/App.axaml.cs index d94e763..523220c 100644 --- a/Needlework.Net/App.axaml.cs +++ b/Needlework.Net/App.axaml.cs @@ -13,17 +13,25 @@ using Needlework.Net.Views.MainWindow; using System; using System.Reactive.Linq; using System.Text.Json; -using System.Threading.Tasks; namespace Needlework.Net; public partial class App : Application, IEnableLogger { - private readonly IServiceProvider _serviceProvider; + private readonly IDataTemplate _viewLocator; + + private readonly IBlobCache _blobCache; + + private readonly PageFactory _pageFactory; + + private readonly MainWindowViewModel _mainWindowViewModel; public App(IServiceProvider serviceProvider) { - _serviceProvider = serviceProvider; + _viewLocator = serviceProvider.GetRequiredService(); + _blobCache = serviceProvider.GetRequiredService(); + _pageFactory = serviceProvider.GetRequiredService(); + _mainWindowViewModel = serviceProvider.GetRequiredService(); this.Log() .Debug("NeedleworkDotNet version: {Version}", AppInfo.Version); @@ -43,30 +51,20 @@ public partial class App : Application, IEnableLogger public override void Initialize() { - DataTemplates.Add(_serviceProvider.GetRequiredService()); + DataTemplates.Add(_viewLocator); AvaloniaXamlLoader.Load(this); } public override void OnFrameworkInitializationCompleted() { - foreach (var page in _serviceProvider.GetServices()) - { - Task.Run(page.InitializeAsync); - } - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - desktop.MainWindow = new MainWindowView() - { - DataContext = _serviceProvider.GetRequiredService() - }; + desktop.MainWindow = new MainWindowView(_mainWindowViewModel, _pageFactory); MainWindow = desktop.MainWindow; - desktop.ShutdownRequested += (_, _) => { - var blobCache = _serviceProvider.GetRequiredService(); - blobCache.Flush().Wait(); - blobCache.Dispose(); + _blobCache.Flush().Wait(); + _blobCache.Dispose(); }; } diff --git a/Needlework.Net/Program.cs b/Needlework.Net/Program.cs index 1e4e510..b7d77bf 100644 --- a/Needlework.Net/Program.cs +++ b/Needlework.Net/Program.cs @@ -137,6 +137,8 @@ class Program builder.AddSingleton(); builder.AddSingleton(); builder.AddSingleton(); + + builder.AddSingleton(); } private static void Program_UnhandledException(object sender, UnhandledExceptionEventArgs e) diff --git a/Needlework.Net/ViewModels/MainWindow/MainWindowViewModel.cs b/Needlework.Net/ViewModels/MainWindow/MainWindowViewModel.cs index d0cbdbb..caba15d 100644 --- a/Needlework.Net/ViewModels/MainWindow/MainWindowViewModel.cs +++ b/Needlework.Net/ViewModels/MainWindow/MainWindowViewModel.cs @@ -1,16 +1,12 @@ -using Avalonia; -using Avalonia.Media; -using Avalonia.Threading; +using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; -using FluentAvalonia.UI.Controls; using Needlework.Net.Constants; using Needlework.Net.Extensions; using Needlework.Net.Helpers; using Needlework.Net.Messages; using Needlework.Net.Services; -using Needlework.Net.ViewModels.Pages; using Needlework.Net.Views.MainWindow; using System; using System.Collections.Generic; @@ -34,21 +30,13 @@ public partial class MainWindowViewModel private readonly SchemaPaneService _schemaPaneService; - public MainWindowViewModel(IEnumerable pages, DialogService dialogService, DocumentService documentService, NotificationService notificationService, SchemaPaneService schemaPaneService) + public MainWindowViewModel(DialogService dialogService, DocumentService documentService, NotificationService notificationService, SchemaPaneService schemaPaneService) { _dialogService = dialogService; _documentService = documentService; _notificationService = notificationService; _schemaPaneService = schemaPaneService; - NavigationViewItems = pages - .OrderBy(p => p.Index) - .ThenBy(p => p.DisplayName) - .Select(ToNavigationViewItem) - .ToList(); - SelectedNavigationViewItem = NavigationViewItems.First(); - CurrentPage = (PageBase)SelectedNavigationViewItem.Tag!; - _notificationService.Notifications.Subscribe(async notification => { var vm = new NotificationViewModel(notification); @@ -92,29 +80,13 @@ public partial class MainWindowViewModel [ObservableProperty] private ObservableCollection _notifications = []; - [ObservableProperty] - private NavigationViewItem _selectedNavigationViewItem; - - [ObservableProperty] - private PageBase _currentPage; - [ObservableProperty] private SchemaSearchDetailsViewModel? _selectedSchemaSearchDetails; - public List NavigationViewItems { get; private set; } = []; - public string AppName => AppInfo.Name; public string Title => $"{AppInfo.Name} {AppInfo.Version}"; - partial void OnSelectedNavigationViewItemChanged(NavigationViewItem value) - { - if (value.Tag is PageBase page) - { - CurrentPage = page; - } - } - partial void OnSelectedSchemaSearchDetailsChanged(SchemaSearchDetailsViewModel? value) { if (value == null) return; @@ -155,25 +127,6 @@ public partial class MainWindowViewModel } } - private NavigationViewItem ToNavigationViewItem(PageBase page) => new() - { - Content = page.DisplayName, - Tag = page, - IconSource = new ImageIconSource - { - Source = new Projektanker.Icons.Avalonia.IconImage() - { - Value = page.Icon, - Brush = new SolidColorBrush(Application.Current!.ActualThemeVariant.Key switch - { - "Light" => Colors.Black, - "Dark" => Colors.White, - _ => Colors.Gray - }) - } - } - }; - public async Task> PopulateAsync(string? searchText, CancellationToken cancellationToken) { if (searchText == null) return []; diff --git a/Needlework.Net/ViewModels/Pages/Console/ConsoleViewModel.cs b/Needlework.Net/ViewModels/Pages/Console/ConsoleViewModel.cs index 495a873..9d5c571 100644 --- a/Needlework.Net/ViewModels/Pages/Console/ConsoleViewModel.cs +++ b/Needlework.Net/ViewModels/Pages/Console/ConsoleViewModel.cs @@ -12,7 +12,7 @@ public partial class ConsoleViewModel : PageBase { private readonly DocumentService _documentService; - public ConsoleViewModel(DocumentService documentService, NotificationService notificationService) : base("Console", "fa-solid fa-terminal", -200) + public ConsoleViewModel(DocumentService documentService, NotificationService notificationService) : base("Console", "fa-solid fa-terminal") { _request = new(notificationService, Endpoints.Tab.LCU); _documentService = documentService; diff --git a/Needlework.Net/ViewModels/Pages/Endpoints/EndpointsViewModel.cs b/Needlework.Net/ViewModels/Pages/Endpoints/EndpointsViewModel.cs index 3772ec5..b1a297b 100644 --- a/Needlework.Net/ViewModels/Pages/Endpoints/EndpointsViewModel.cs +++ b/Needlework.Net/ViewModels/Pages/Endpoints/EndpointsViewModel.cs @@ -22,7 +22,7 @@ public partial class EndpointsViewModel : PageBase private readonly NotificationService _notificationService; - public EndpointsViewModel(DocumentService documentService, NotificationService notificationService) : base("Endpoints", "fa-solid fa-rectangle-list", -500) + public EndpointsViewModel(DocumentService documentService, NotificationService notificationService) : base("Endpoints", "fa-solid fa-rectangle-list") { _documentService = documentService; _notificationService = notificationService; diff --git a/Needlework.Net/ViewModels/Pages/Home/HomeViewModel.cs b/Needlework.Net/ViewModels/Pages/Home/HomeViewModel.cs index 28f7b69..34b767c 100644 --- a/Needlework.Net/ViewModels/Pages/Home/HomeViewModel.cs +++ b/Needlework.Net/ViewModels/Pages/Home/HomeViewModel.cs @@ -21,7 +21,7 @@ public partial class HomeViewModel : PageBase, IEnableLogger private readonly IDisposable _carouselNextDisposable; - public HomeViewModel(HextechDocsService hextechDocsService) : base("Home", "fa-solid fa-house", int.MinValue) + public HomeViewModel(HextechDocsService hextechDocsService) : base("Home", "fa-solid fa-house") { _hextechDocsService = hextechDocsService; diff --git a/Needlework.Net/ViewModels/Pages/PageBase.cs b/Needlework.Net/ViewModels/Pages/PageBase.cs index 0d681e9..2b581bb 100644 --- a/Needlework.Net/ViewModels/Pages/PageBase.cs +++ b/Needlework.Net/ViewModels/Pages/PageBase.cs @@ -4,11 +4,11 @@ using System.Threading.Tasks; namespace Needlework.Net.ViewModels.Pages; -public abstract partial class PageBase(string displayName, string icon, int index = 0) : ObservableValidator +public abstract partial class PageBase(string displayName, string icon) : ObservableValidator { - [ObservableProperty] private string _displayName = displayName; - [ObservableProperty] private string _icon = icon; - [ObservableProperty] private int _index = index; + public string DisplayName { get; } = displayName; + + public string Icon { get; } = icon; public abstract Task InitializeAsync(); } \ No newline at end of file diff --git a/Needlework.Net/ViewModels/Pages/PageFactory.cs b/Needlework.Net/ViewModels/Pages/PageFactory.cs new file mode 100644 index 0000000..3a721ec --- /dev/null +++ b/Needlework.Net/ViewModels/Pages/PageFactory.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Needlework.Net.ViewModels.Pages +{ + public class PageFactory + { + private readonly IEnumerable _pages; + + public PageFactory(IEnumerable pages) + { + _pages = pages; + } + + public PageBase GetPage() where T : PageBase + { + var page = _pages.Where(page => typeof(T) == page.GetType()) + .FirstOrDefault() ?? throw new NotSupportedException(typeof(T).FullName); + Task.Run(page.InitializeAsync); + return page; + } + } +} diff --git a/Needlework.Net/ViewModels/Pages/Schemas/SchemasViewModel.cs b/Needlework.Net/ViewModels/Pages/Schemas/SchemasViewModel.cs index 7409025..e07e7e8 100644 --- a/Needlework.Net/ViewModels/Pages/Schemas/SchemasViewModel.cs +++ b/Needlework.Net/ViewModels/Pages/Schemas/SchemasViewModel.cs @@ -22,7 +22,7 @@ namespace Needlework.Net.ViewModels.Pages.Schemas private List _schemas = []; - public SchemasViewModel(DocumentService documentService, SchemaPaneService schemaPaneService) : base("Schemas", "fa-solid fa-file-lines", -100) + public SchemasViewModel(DocumentService documentService, SchemaPaneService schemaPaneService) : base("Schemas", "fa-solid fa-file-lines") { _documentService = documentService; _schemaPaneService = schemaPaneService; diff --git a/Needlework.Net/ViewModels/Pages/Websocket/WebsocketViewModel.cs b/Needlework.Net/ViewModels/Pages/Websocket/WebsocketViewModel.cs index 3bbf9e2..e550986 100644 --- a/Needlework.Net/ViewModels/Pages/Websocket/WebsocketViewModel.cs +++ b/Needlework.Net/ViewModels/Pages/Websocket/WebsocketViewModel.cs @@ -35,7 +35,7 @@ public partial class WebSocketViewModel : PageBase, IEnableLogger private readonly object _tokenLock = new(); - public WebSocketViewModel(IFlurlClientCache clients, NotificationService notificationService) : base("Event Viewer", "fa-solid fa-plug", -100) + public WebSocketViewModel(IFlurlClientCache clients, NotificationService notificationService) : base("Event Viewer", "fa-solid fa-plug") { _githubUserContentClient = clients.Get(FlurlClientKeys.GithubUserContentClient); _notificationService = notificationService; diff --git a/Needlework.Net/Views/MainWindow/MainWindowView.axaml b/Needlework.Net/Views/MainWindow/MainWindowView.axaml index 46533da..c7e98d7 100644 --- a/Needlework.Net/Views/MainWindow/MainWindowView.axaml +++ b/Needlework.Net/Views/MainWindow/MainWindowView.axaml @@ -66,13 +66,12 @@ OpenPaneLength="350" PanePlacement="Right"> + PaneDisplayMode="Left" + IsSettingsVisible="False" + IsPaneOpen="False" + OpenPaneLength="200" + Grid.Row="1" + Name="NavigationView"> @@ -105,7 +104,7 @@ - + diff --git a/Needlework.Net/Views/MainWindow/MainWindowView.axaml.cs b/Needlework.Net/Views/MainWindow/MainWindowView.axaml.cs index 09abd45..9e4fbb6 100644 --- a/Needlework.Net/Views/MainWindow/MainWindowView.axaml.cs +++ b/Needlework.Net/Views/MainWindow/MainWindowView.axaml.cs @@ -4,7 +4,18 @@ using Avalonia.Interactivity; using Avalonia.Media; using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Windowing; +using Needlework.Net.ViewModels.MainWindow; +using Needlework.Net.ViewModels.Pages; +using Needlework.Net.ViewModels.Pages.About; +using Needlework.Net.ViewModels.Pages.Console; +using Needlework.Net.ViewModels.Pages.Endpoints; +using Needlework.Net.ViewModels.Pages.Home; +using Needlework.Net.ViewModels.Pages.Schemas; +using Needlework.Net.ViewModels.Pages.Settings; +using Needlework.Net.ViewModels.Pages.WebSocket; using System; +using System.Collections.Generic; +using System.Linq; namespace Needlework.Net.Views.MainWindow; @@ -13,34 +24,74 @@ public partial class MainWindowView : AppWindow public MainWindowView() { InitializeComponent(); + } + public MainWindowView(MainWindowViewModel mainWindowViewModel, PageFactory pageFactory) + { + InitializeComponent(); + + DataContext = mainWindowViewModel; TitleBar.ExtendsContentIntoTitleBar = true; TitleBar.TitleBarHitTestType = TitleBarHitTestType.Complex; TransparencyLevelHint = [WindowTransparencyLevel.Mica, WindowTransparencyLevel.None]; Background = IsWindows11 ? null : Background; + NavigationView.MenuItems = [.. new List() + { + pageFactory.GetPage(), + pageFactory.GetPage(), + pageFactory.GetPage(), + pageFactory.GetPage(), + pageFactory.GetPage(), + pageFactory.GetPage(), + pageFactory.GetPage(), + } + .Select(ToNavigationViewItem)]; + NavigationView.GetObservable(NavigationView.SelectedItemProperty) + .Subscribe(value => + { + if (value is NavigationViewItem item) + { + CurrentPageContentControl.Content = item.Tag; + } + }); + NavigationView.SelectedItem = NavigationView.MenuItems.Cast() + .First(); + SchemaAutoCompleteBox.MinimumPopulateDelay = TimeSpan.FromSeconds(1); SchemaAutoCompleteBox.MinimumPrefixLength = 3; + App.Current!.TryGetResource("TextFillColorPrimaryBrush", ActualThemeVariant, out var brush); CloseCommandBarButton.IconSource = new ImageIconSource { Source = new Projektanker.Icons.Avalonia.IconImage() { Value = "fa-solid fa-file-circle-xmark", - Brush = new SolidColorBrush(Application.Current!.ActualThemeVariant.Key switch + Brush = (SolidColorBrush)brush! + } + }; + } + + private NavigationViewItem ToNavigationViewItem(PageBase page) + { + App.Current!.TryGetResource("TextFillColorPrimaryBrush", ActualThemeVariant, out var brush); + return new NavigationViewItem() + { + Content = page.DisplayName, + Tag = page, + IconSource = new ImageIconSource + { + Source = new Projektanker.Icons.Avalonia.IconImage() { - "Light" => Colors.Black, - "Dark" => Colors.White, - _ => Colors.Gray - }) + Value = page.Icon, + Brush = (SolidColorBrush)brush! + } } }; } protected override void OnLoaded(RoutedEventArgs e) { - base.OnLoaded(e); - if (VisualRoot is AppWindow aw) { TitleBarHost.ColumnDefinitions[3].Width = new GridLength(aw.TitleBar.RightInset, GridUnitType.Pixel);