diff --git a/DiscordChatExporter.Gui/App.axaml.cs b/DiscordChatExporter.Gui/App.axaml.cs index bb27f1d6..42e0f6d6 100644 --- a/DiscordChatExporter.Gui/App.axaml.cs +++ b/DiscordChatExporter.Gui/App.axaml.cs @@ -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(); _services = services.BuildServiceProvider(true); - _settingsService = _services.GetRequiredService(); - _mainViewModel = _services.GetRequiredService().CreateMainViewModel(); // Re-initialize the theme when the user changes it _eventRoot.Add( - _settingsService.WatchProperty( - o => o.Theme, - () => - { - RequestedThemeVariant = _settingsService.Theme switch + _services + .GetRequiredService() + .WatchProperty( + o => o.Theme, + () => { - ThemeVariant.Light => Avalonia.Styling.ThemeVariant.Light, - ThemeVariant.Dark => Avalonia.Styling.ThemeVariant.Dark, - _ => Avalonia.Styling.ThemeVariant.Default, - }; + RequestedThemeVariant = _services + .GetRequiredService() + .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() + .TryBindWindow( + _services.GetRequiredService().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().Load(); } private void Application_OnActualThemeVariantChanged(object? sender, EventArgs args) => diff --git a/DiscordChatExporter.Gui/Framework/ViewManager.cs b/DiscordChatExporter.Gui/Framework/ViewManager.cs index 40967783..f4d14e95 100644 --- a/DiscordChatExporter.Gui/Framework/ViewManager.cs +++ b/DiscordChatExporter.Gui/Framework/ViewManager.cs @@ -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? TryBindUserControl(T viewModel) + where T : ViewModelBase => TryBindView(viewModel) as UserControl; + + public Window? TryBindWindow(T viewModel) + where T : ViewModelBase => TryBindView(viewModel) as Window; } public partial class ViewManager : IDataTemplate diff --git a/DiscordChatExporter.Gui/Framework/ViewModelBase.cs b/DiscordChatExporter.Gui/Framework/ViewModelBase.cs index 2f8a0076..27b7b2a5 100644 --- a/DiscordChatExporter.Gui/Framework/ViewModelBase.cs +++ b/DiscordChatExporter.Gui/Framework/ViewModelBase.cs @@ -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() diff --git a/DiscordChatExporter.Gui/ViewModels/Components/DashboardViewModel.cs b/DiscordChatExporter.Gui/ViewModels/Components/DashboardViewModel.cs index 71e9429f..86f3f2f2 100644 --- a/DiscordChatExporter.Gui/ViewModels/Components/DashboardViewModel.cs +++ b/DiscordChatExporter.Gui/ViewModels/Components/DashboardViewModel.cs @@ -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 SelectedChannels { get; } = []; - [RelayCommand] - private void Initialize() + public override Task InitializeAsync() { if (!string.IsNullOrWhiteSpace(_settingsService.LastToken)) Token = _settingsService.LastToken; + + return Task.CompletedTask; } [RelayCommand] diff --git a/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs b/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs index a5e57705..5a43e646 100644 --- a/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs +++ b/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs @@ -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] diff --git a/DiscordChatExporter.Gui/ViewModels/MainViewModel.cs b/DiscordChatExporter.Gui/ViewModels/MainViewModel.cs index 77060b28..9238e01c 100644 --- a/DiscordChatExporter.Gui/ViewModels/MainViewModel.cs +++ b/DiscordChatExporter.Gui/ViewModels/MainViewModel.cs @@ -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(); diff --git a/DiscordChatExporter.Gui/Views/Components/DashboardView.axaml.cs b/DiscordChatExporter.Gui/Views/Components/DashboardView.axaml.cs index c9135233..709bd542 100644 --- a/DiscordChatExporter.Gui/Views/Components/DashboardView.axaml.cs +++ b/DiscordChatExporter.Gui/Views/Components/DashboardView.axaml.cs @@ -12,11 +12,8 @@ public partial class DashboardView : UserControl { 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, diff --git a/DiscordChatExporter.Gui/Views/Dialogs/ExportSetupView.axaml b/DiscordChatExporter.Gui/Views/Dialogs/ExportSetupView.axaml index ae91a35f..dbd92970 100644 --- a/DiscordChatExporter.Gui/Views/Dialogs/ExportSetupView.axaml +++ b/DiscordChatExporter.Gui/Views/Dialogs/ExportSetupView.axaml @@ -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"> - \ No newline at end of file + diff --git a/DiscordChatExporter.Gui/Views/Dialogs/ExportSetupView.axaml.cs b/DiscordChatExporter.Gui/Views/Dialogs/ExportSetupView.axaml.cs index 07707116..f95d8b93 100644 --- a/DiscordChatExporter.Gui/Views/Dialogs/ExportSetupView.axaml.cs +++ b/DiscordChatExporter.Gui/Views/Dialogs/ExportSetupView.axaml.cs @@ -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 { public ExportSetupView() => InitializeComponent(); - - private void UserControl_OnLoaded(object? sender, RoutedEventArgs args) => - DataContext.InitializeCommand.Execute(null); } diff --git a/DiscordChatExporter.Gui/Views/MainView.axaml b/DiscordChatExporter.Gui/Views/MainView.axaml index f78dee0b..c8be563d 100644 --- a/DiscordChatExporter.Gui/Views/MainView.axaml +++ b/DiscordChatExporter.Gui/Views/MainView.axaml @@ -14,12 +14,9 @@ Icon="/favicon.ico" RenderOptions.BitmapInterpolationMode="HighQuality" WindowStartupLocation="CenterScreen"> - + - \ No newline at end of file + diff --git a/DiscordChatExporter.Gui/Views/MainView.axaml.cs b/DiscordChatExporter.Gui/Views/MainView.axaml.cs index 9e3b4767..c8af1889 100644 --- a/DiscordChatExporter.Gui/Views/MainView.axaml.cs +++ b/DiscordChatExporter.Gui/Views/MainView.axaml.cs @@ -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 { public MainView() => InitializeComponent(); - - private void DialogHost_OnLoaded(object? sender, RoutedEventArgs args) => - DataContext.InitializeCommand.Execute(null); }