refactor: page factory and less mvvm-breaking

This commit is contained in:
estrogen elf
2025-06-23 09:37:30 -05:00
parent c78f75a332
commit 471559d987
12 changed files with 118 additions and 90 deletions

View File

@@ -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<IDataTemplate>();
_blobCache = serviceProvider.GetRequiredService<IBlobCache>();
_pageFactory = serviceProvider.GetRequiredService<PageFactory>();
_mainWindowViewModel = serviceProvider.GetRequiredService<MainWindowViewModel>();
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<IDataTemplate>());
DataTemplates.Add(_viewLocator);
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
foreach (var page in _serviceProvider.GetServices<PageBase>())
{
Task.Run(page.InitializeAsync);
}
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindowView()
{
DataContext = _serviceProvider.GetRequiredService<MainWindowViewModel>()
};
desktop.MainWindow = new MainWindowView(_mainWindowViewModel, _pageFactory);
MainWindow = desktop.MainWindow;
desktop.ShutdownRequested += (_, _) =>
{
var blobCache = _serviceProvider.GetRequiredService<IBlobCache>();
blobCache.Flush().Wait();
blobCache.Dispose();
_blobCache.Flush().Wait();
_blobCache.Dispose();
};
}

View File

@@ -137,6 +137,8 @@ class Program
builder.AddSingleton<PageBase, SchemasViewModel>();
builder.AddSingleton<PageBase, AboutViewModel>();
builder.AddSingleton<PageBase, SettingsViewModel>();
builder.AddSingleton<PageFactory>();
}
private static void Program_UnhandledException(object sender, UnhandledExceptionEventArgs e)

View File

@@ -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<PageBase> 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<NotificationViewModel> _notifications = [];
[ObservableProperty]
private NavigationViewItem _selectedNavigationViewItem;
[ObservableProperty]
private PageBase _currentPage;
[ObservableProperty]
private SchemaSearchDetailsViewModel? _selectedSchemaSearchDetails;
public List<NavigationViewItem> 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<IEnumerable<object>> PopulateAsync(string? searchText, CancellationToken cancellationToken)
{
if (searchText == null) return [];

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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<PageBase> _pages;
public PageFactory(IEnumerable<PageBase> pages)
{
_pages = pages;
}
public PageBase GetPage<T>() 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;
}
}
}

View File

@@ -22,7 +22,7 @@ namespace Needlework.Net.ViewModels.Pages.Schemas
private List<SchemaSearchDetailsViewModel> _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;

View File

@@ -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;

View File

@@ -71,8 +71,7 @@
IsPaneOpen="False"
OpenPaneLength="200"
Grid.Row="1"
MenuItemsSource="{Binding NavigationViewItems}"
SelectedItem="{Binding SelectedNavigationViewItem}">
Name="NavigationView">
<ui:NavigationView.PaneFooter>
<StackPanel Orientation="Vertical">
<StackPanel.Styles>
@@ -105,7 +104,7 @@
</StackPanel>
</ui:NavigationView.PaneFooter>
<Grid>
<TransitioningContentControl Content="{Binding CurrentPage}"/>
<TransitioningContentControl Name="CurrentPageContentControl"/>
<ItemsControl ItemsSource="{Binding Notifications}"
VerticalAlignment="Bottom">
<ItemsControl.ItemTemplate>

View File

@@ -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<PageBase>()
{
pageFactory.GetPage<HomeViewModel>(),
pageFactory.GetPage<EndpointsViewModel>(),
pageFactory.GetPage<ConsoleViewModel>(),
pageFactory.GetPage<WebSocketViewModel>(),
pageFactory.GetPage<SchemasViewModel>(),
pageFactory.GetPage<SettingsViewModel>(),
pageFactory.GetPage<AboutViewModel>(),
}
.Select(ToNavigationViewItem)];
NavigationView.GetObservable(NavigationView.SelectedItemProperty)
.Subscribe(value =>
{
if (value is NavigationViewItem item)
{
CurrentPageContentControl.Content = item.Tag;
}
});
NavigationView.SelectedItem = NavigationView.MenuItems.Cast<NavigationViewItem>()
.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)
{
"Light" => Colors.Black,
"Dark" => Colors.White,
_ => Colors.Gray
})
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()
{
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);