mirror of
https://github.com/BlossomiShymae/Needlework.Net.git
synced 2025-12-06 02:00:47 +01:00
refactor: page factory and less mvvm-breaking
This commit is contained in:
@@ -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();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 [];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
25
Needlework.Net/ViewModels/Pages/PageFactory.cs
Normal file
25
Needlework.Net/ViewModels/Pages/PageFactory.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -66,13 +66,12 @@
|
||||
OpenPaneLength="350"
|
||||
PanePlacement="Right">
|
||||
<ui:NavigationView AlwaysShowHeader="False"
|
||||
PaneDisplayMode="Left"
|
||||
IsSettingsVisible="False"
|
||||
IsPaneOpen="False"
|
||||
OpenPaneLength="200"
|
||||
Grid.Row="1"
|
||||
MenuItemsSource="{Binding NavigationViewItems}"
|
||||
SelectedItem="{Binding SelectedNavigationViewItem}">
|
||||
PaneDisplayMode="Left"
|
||||
IsSettingsVisible="False"
|
||||
IsPaneOpen="False"
|
||||
OpenPaneLength="200"
|
||||
Grid.Row="1"
|
||||
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>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user