Automate view initialization

This commit is contained in:
tyrrrz
2026-04-02 14:35:17 +03:00
parent 7ee2763d4b
commit f6166764e9
11 changed files with 62 additions and 66 deletions

View File

@@ -12,7 +12,6 @@ using DiscordChatExporter.Gui.Utils.Extensions;
using DiscordChatExporter.Gui.ViewModels;
using DiscordChatExporter.Gui.ViewModels.Components;
using DiscordChatExporter.Gui.ViewModels.Dialogs;
using DiscordChatExporter.Gui.Views;
using Material.Styles.Themes;
using Microsoft.Extensions.DependencyInjection;
@@ -20,11 +19,8 @@ namespace DiscordChatExporter.Gui;
public class App : Application, IDisposable
{
private readonly DisposableCollector _eventRoot = new();
private readonly ServiceProvider _services;
private readonly SettingsService _settingsService;
private readonly MainViewModel _mainViewModel;
private readonly DisposableCollector _eventRoot = new();
private bool _isDisposed;
@@ -53,35 +49,30 @@ public class App : Application, IDisposable
services.AddTransient<SettingsViewModel>();
_services = services.BuildServiceProvider(true);
_settingsService = _services.GetRequiredService<SettingsService>();
_mainViewModel = _services.GetRequiredService<ViewModelManager>().CreateMainViewModel();
// Re-initialize the theme when the user changes it
_eventRoot.Add(
_settingsService.WatchProperty(
o => o.Theme,
() =>
{
RequestedThemeVariant = _settingsService.Theme switch
_services
.GetRequiredService<SettingsService>()
.WatchProperty(
o => o.Theme,
() =>
{
ThemeVariant.Light => Avalonia.Styling.ThemeVariant.Light,
ThemeVariant.Dark => Avalonia.Styling.ThemeVariant.Dark,
_ => Avalonia.Styling.ThemeVariant.Default,
};
RequestedThemeVariant = _services
.GetRequiredService<SettingsService>()
.Theme switch
{
ThemeVariant.Light => Avalonia.Styling.ThemeVariant.Light,
ThemeVariant.Dark => Avalonia.Styling.ThemeVariant.Dark,
_ => Avalonia.Styling.ThemeVariant.Default,
};
InitializeTheme();
}
)
InitializeTheme();
}
)
);
}
public override void Initialize()
{
base.Initialize();
AvaloniaXamlLoader.Load(this);
}
private void InitializeTheme()
{
var actualTheme = RequestedThemeVariant?.Key switch
@@ -97,25 +88,28 @@ public class App : Application, IDisposable
: Theme.Create(Theme.Dark, Color.Parse("#E8E8E8"), Color.Parse("#F9A825"));
}
public override void Initialize()
{
base.Initialize();
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainView { DataContext = _mainViewModel };
void OnExit(object? sender, ControlledApplicationLifetimeExitEventArgs args)
{
if (sender is IControlledApplicationLifetime lifetime)
lifetime.Exit -= OnExit;
Dispose();
}
desktop.MainWindow = _services
.GetRequiredService<ViewManager>()
.TryBindWindow(
_services.GetRequiredService<ViewModelManager>().CreateMainViewModel()
);
// Although `App.Dispose()` is invoked from `Program.Main(...)`, on some platforms
// it may be called too late in the shutdown lifecycle. Attach an exit
// handler to ensure timely disposal as a safeguard.
// https://github.com/Tyrrrz/YoutubeDownloader/issues/795
desktop.Exit += OnExit;
desktop.Exit += (_, _) => Dispose();
}
base.OnFrameworkInitializationCompleted();
@@ -124,7 +118,7 @@ public class App : Application, IDisposable
InitializeTheme();
// Load settings
_settingsService.Load();
_services.GetRequiredService<SettingsService>().Load();
}
private void Application_OnActualThemeVariantChanged(object? sender, EventArgs args) =>

View File

@@ -30,8 +30,23 @@ public partial class ViewManager
view.DataContext ??= viewModel;
if (view.IsInitialized)
{
_ = viewModel.InitializeAsync();
}
else
{
view.Initialized += async (_, _) => await viewModel.InitializeAsync();
}
return view;
}
public UserControl<T>? TryBindUserControl<T>(T viewModel)
where T : ViewModelBase => TryBindView(viewModel) as UserControl<T>;
public Window<T>? TryBindWindow<T>(T viewModel)
where T : ViewModelBase => TryBindView(viewModel) as Window<T>;
}
public partial class ViewManager : IDataTemplate

View File

@@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
namespace DiscordChatExporter.Gui.Framework;
@@ -9,6 +10,8 @@ public abstract class ViewModelBase : ObservableObject, IDisposable
protected void OnAllPropertiesChanged() => OnPropertyChanged(string.Empty);
public virtual Task InitializeAsync() => Task.CompletedTask;
protected virtual void Dispose(bool disposing) { }
public void Dispose()

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -96,11 +95,12 @@ public partial class DashboardViewModel : ViewModelBase
public ObservableCollection<ChannelConnection> SelectedChannels { get; } = [];
[RelayCommand]
private void Initialize()
public override Task InitializeAsync()
{
if (!string.IsNullOrWhiteSpace(_settingsService.LastToken))
Token = _settingsService.LastToken;
return Task.CompletedTask;
}
[RelayCommand]

View File

@@ -102,8 +102,7 @@ public partial class ExportSetupViewModel(
? MessageFilter.Parse(MessageFilterValue)
: MessageFilter.Null;
[RelayCommand]
private void Initialize()
public override Task InitializeAsync()
{
// Persist preferences
SelectedFormat = settingsService.LastExportFormat;
@@ -126,6 +125,8 @@ public partial class ExportSetupViewModel(
|| ShouldReuseAssets
|| !string.IsNullOrWhiteSpace(AssetsDirPath)
|| IsReverseMessageOrder;
return Task.CompletedTask;
}
[RelayCommand]

View File

@@ -2,7 +2,6 @@
using System.Diagnostics;
using System.Threading.Tasks;
using Avalonia;
using CommunityToolkit.Mvvm.Input;
using DiscordChatExporter.Gui.Framework;
using DiscordChatExporter.Gui.Localization;
using DiscordChatExporter.Gui.Services;
@@ -100,8 +99,7 @@ public partial class MainViewModel(
}
}
[RelayCommand]
private async Task InitializeAsync()
public override async Task InitializeAsync()
{
await ShowUkraineSupportMessageAsync();
await ShowDevelopmentBuildMessageAsync();

View File

@@ -12,11 +12,8 @@ public partial class DashboardView : UserControl<DashboardViewModel>
{
public DashboardView() => InitializeComponent();
private void UserControl_OnLoaded(object? sender, RoutedEventArgs args)
{
DataContext.InitializeCommand.Execute(null);
private void UserControl_OnLoaded(object? sender, RoutedEventArgs args) =>
TokenValueTextBox.Focus();
}
private void AvailableGuildsListBox_OnSelectionChanged(
object? sender,

View File

@@ -10,8 +10,7 @@
xmlns:utils="clr-namespace:DiscordChatExporter.Gui.Utils"
x:Name="UserControl"
Width="380"
x:DataType="dialogs:ExportSetupViewModel"
Loaded="UserControl_OnLoaded">
x:DataType="dialogs:ExportSetupViewModel">
<Grid RowDefinitions="Auto,*,Auto">
<!-- Guild/channel info -->
<Grid
@@ -292,4 +291,4 @@
Theme="{DynamicResource MaterialOutlineButton}" />
</Grid>
</Grid>
</UserControl>
</UserControl>

View File

@@ -1,4 +1,3 @@
using Avalonia.Interactivity;
using DiscordChatExporter.Gui.Framework;
using DiscordChatExporter.Gui.ViewModels.Dialogs;
@@ -7,7 +6,4 @@ namespace DiscordChatExporter.Gui.Views.Dialogs;
public partial class ExportSetupView : UserControl<ExportSetupViewModel>
{
public ExportSetupView() => InitializeComponent();
private void UserControl_OnLoaded(object? sender, RoutedEventArgs args) =>
DataContext.InitializeCommand.Execute(null);
}

View File

@@ -14,12 +14,9 @@
Icon="/favicon.ico"
RenderOptions.BitmapInterpolationMode="HighQuality"
WindowStartupLocation="CenterScreen">
<dialogHostAvalonia:DialogHost
x:Name="DialogHost"
CloseOnClickAway="False"
Loaded="DialogHost_OnLoaded">
<dialogHostAvalonia:DialogHost x:Name="DialogHost" CloseOnClickAway="False">
<materialStyles:SnackbarHost HostName="Root" SnackbarMaxCounts="3">
<ContentControl Content="{Binding Dashboard}" />
</materialStyles:SnackbarHost>
</dialogHostAvalonia:DialogHost>
</Window>
</Window>

View File

@@ -1,5 +1,4 @@
using Avalonia.Interactivity;
using DiscordChatExporter.Gui.Framework;
using DiscordChatExporter.Gui.Framework;
using DiscordChatExporter.Gui.ViewModels;
namespace DiscordChatExporter.Gui.Views;
@@ -7,7 +6,4 @@ namespace DiscordChatExporter.Gui.Views;
public partial class MainView : Window<MainViewModel>
{
public MainView() => InitializeComponent();
private void DialogHost_OnLoaded(object? sender, RoutedEventArgs args) =>
DataContext.InitializeCommand.Execute(null);
}