From cf24697f98554a88a10b196d959938052efc1a9b Mon Sep 17 00:00:00 2001 From: gaviny82 <38808970+gaviny82@users.noreply.github.com> Date: Sun, 22 Dec 2024 19:55:33 +0800 Subject: [PATCH] [Infra] Dialog services (#297) * Add dialog services to DI * Add dialog types and view models to DI * Rename IDialogActivationService methods * Refactor AddVmArgumentDialog * Refactor AuthenticationWizardDialog * Refactor DeleteInstanceDialog * Refactor DownloadResourceDialog * Refactor ExceptionDialog * Refactor SwitchAccountDialog * Refactor UploadSkinDialog --- Natsurainko.FluentLauncher/App.xaml.cs | 2 +- Natsurainko.FluentLauncher/Program.cs | 13 +++++++ .../Common/AddVmArgumentDialogViewModel.cs | 11 +++--- .../Common/DeleteInstanceDialogViewModel.cs | 12 ++++--- .../Common/DownloadResourceDialogViewModel.cs | 36 +++++++++++++------ .../Common/UploadSkinDialogViewModel.cs | 11 +++--- .../Cores/Manage/ConfigViewModel.cs | 13 +++---- .../Cores/Manage/DefaultViewModel.cs | 12 ++++--- .../ViewModels/Downloads/DefaultViewModel.cs | 9 +++-- .../ViewModels/Downloads/DetailsViewModel.cs | 8 +++-- .../ViewModels/Downloads/SearchViewModel.cs | 9 +++-- .../ViewModels/OOBE/OOBEViewModel.cs | 9 +++-- .../ViewModels/Settings/AccountViewModel.cs | 18 +++++----- .../ViewModels/Settings/SkinViewModel.cs | 11 ++++-- .../Views/Common/AddVmArgumentDialog.xaml.cs | 4 +-- .../Common/AuthenticationWizardDialog.xaml.cs | 8 +---- .../Views/Common/DeleteInstanceDialog.xaml.cs | 4 +-- .../Common/DownloadResourceDialog.xaml.cs | 4 +-- .../Views/Common/ExceptionDialog.xaml.cs | 3 +- .../Views/Common/SwitchAccountDialog.xaml.cs | 3 +- .../Views/Common/UploadSkinDialog.xaml.cs | 4 +-- .../Dialogs/IDialogActivationService.cs | 4 +-- .../Dialogs/IDialogParameterAware.cs | 2 +- .../AppHost/WinUIApplicationBuilder.cs | 26 +++++++++++++- .../Dialogs/WinUIDialogActivationService.cs | 26 +++++++++++--- .../Windows/WinUIWindowService.cs | 14 ++++---- 26 files changed, 181 insertions(+), 95 deletions(-) diff --git a/Natsurainko.FluentLauncher/App.xaml.cs b/Natsurainko.FluentLauncher/App.xaml.cs index 2df7f629..1ae2778e 100644 --- a/Natsurainko.FluentLauncher/App.xaml.cs +++ b/Natsurainko.FluentLauncher/App.xaml.cs @@ -120,7 +120,7 @@ public static void ShowErrorMessage(string errorMessage) { try { - await new ExceptionDialog(errorMessage).ShowAsync(); + await new ExceptionDialog(errorMessage) { XamlRoot = MainWindow.XamlRoot }.ShowAsync(); } catch { diff --git a/Natsurainko.FluentLauncher/Program.cs b/Natsurainko.FluentLauncher/Program.cs index e454f69e..36ca8ad3 100644 --- a/Natsurainko.FluentLauncher/Program.cs +++ b/Natsurainko.FluentLauncher/Program.cs @@ -84,6 +84,19 @@ #endregion +#region Configure WinUI dialogs + +var dialogs = builder.Dialogs; + +dialogs.WithDialog("AddVmArgumentDialog"); +dialogs.WithDialog("AuthenticationWizardDialog"); +dialogs.WithDialog("DeleteInstanceDialog"); +dialogs.WithDialog("DownloadResourceDialog"); +dialogs.WithDialog("SwitchAccountDialog"); +dialogs.WithDialog("UploadSkinDialog"); + +#endregion + #region Services var services = builder.Services; diff --git a/Natsurainko.FluentLauncher/ViewModels/Common/AddVmArgumentDialogViewModel.cs b/Natsurainko.FluentLauncher/ViewModels/Common/AddVmArgumentDialogViewModel.cs index eb36f0db..828a231f 100644 --- a/Natsurainko.FluentLauncher/ViewModels/Common/AddVmArgumentDialogViewModel.cs +++ b/Natsurainko.FluentLauncher/ViewModels/Common/AddVmArgumentDialogViewModel.cs @@ -1,5 +1,6 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using FluentLauncher.Infra.UI.Dialogs; using Microsoft.UI.Xaml.Controls; using Natsurainko.FluentLauncher.Utils; using System; @@ -7,18 +8,20 @@ namespace Natsurainko.FluentLauncher.ViewModels.Common; -internal partial class AddVmArgumentDialogViewModel : ObservableObject +internal partial class AddVmArgumentDialogViewModel : ObservableObject, IDialogParameterAware { [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(AddCommand))] public partial string? Argument { get; set; } - private readonly Action _addAction; + private Action _addAction = null!; private ContentDialog _dialog = null!; - public AddVmArgumentDialogViewModel(Action addAction) + public AddVmArgumentDialogViewModel() { } + + void IDialogParameterAware.HandleParameter(object param) { - _addAction = addAction; + _addAction = (Action)param; } [RelayCommand] diff --git a/Natsurainko.FluentLauncher/ViewModels/Common/DeleteInstanceDialogViewModel.cs b/Natsurainko.FluentLauncher/ViewModels/Common/DeleteInstanceDialogViewModel.cs index f720ae37..0e104cd6 100644 --- a/Natsurainko.FluentLauncher/ViewModels/Common/DeleteInstanceDialogViewModel.cs +++ b/Natsurainko.FluentLauncher/ViewModels/Common/DeleteInstanceDialogViewModel.cs @@ -1,5 +1,6 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using FluentLauncher.Infra.UI.Dialogs; using FluentLauncher.Infra.UI.Navigation; using Microsoft.UI.Xaml.Controls; using Natsurainko.FluentLauncher.Services.Launch; @@ -13,12 +14,12 @@ namespace Natsurainko.FluentLauncher.ViewModels.Common; -internal partial class DeleteInstanceDialogViewModel : ObservableObject +internal partial class DeleteInstanceDialogViewModel : ObservableObject, IDialogParameterAware { - private readonly MinecraftInstance _minecraftInstance; private readonly INavigationService _navigationService; private readonly NotificationService _notificationService; private readonly GameService _gameService; + private MinecraftInstance _minecraftInstance = null!; private ContentDialog _dialog = null!; // Set in LoadEvent @@ -28,17 +29,20 @@ internal partial class DeleteInstanceDialogViewModel : ObservableObject public partial bool DeleteCoreSettings { get; set; } = true; public DeleteInstanceDialogViewModel( - MinecraftInstance minecraftInstance, INavigationService navigationService, NotificationService notificationService, GameService gameService) { - _minecraftInstance = minecraftInstance; _navigationService = navigationService; _notificationService = notificationService; _gameService = gameService; } + void IDialogParameterAware.HandleParameter(object param) + { + _minecraftInstance = (MinecraftInstance)param; + } + [RelayCommand] public void LoadEvent(object args) { diff --git a/Natsurainko.FluentLauncher/ViewModels/Common/DownloadResourceDialogViewModel.cs b/Natsurainko.FluentLauncher/ViewModels/Common/DownloadResourceDialogViewModel.cs index 4558214f..966c8a79 100644 --- a/Natsurainko.FluentLauncher/ViewModels/Common/DownloadResourceDialogViewModel.cs +++ b/Natsurainko.FluentLauncher/ViewModels/Common/DownloadResourceDialogViewModel.cs @@ -1,5 +1,6 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using FluentLauncher.Infra.UI.Dialogs; using FluentLauncher.Infra.UI.Navigation; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -22,31 +23,44 @@ #nullable disable namespace Natsurainko.FluentLauncher.ViewModels.Common; -internal partial class DownloadResourceDialogViewModel : ObservableObject +internal partial class DownloadResourceDialogViewModel : ObservableObject, IDialogParameterAware { private ContentDialog _dialog; private GameResourceFile[] ResourceFileItems; - private readonly object _resource; - private readonly INavigationService _navigationService; + private readonly CurseForgeClient _curseForgeClient; + private readonly ModrinthClient _modrinthClient; - private readonly CurseForgeClient _curseForgeClient = App.GetService(); - private readonly ModrinthClient _modrinthClient = App.GetService(); + private readonly GameService _gameService; + private readonly DownloadService _downloadService; + private readonly NotificationService _notificationService; - private readonly GameService _gameService = App.GetService(); - private readonly DownloadService _downloadService = App.GetService(); - private readonly NotificationService _notificationService = App.GetService(); + private object _resource = null!; public MinecraftInstance MinecraftInstance { get; private set; } - public DownloadResourceDialogViewModel(object resource, INavigationService navigationService) + public DownloadResourceDialogViewModel( + CurseForgeClient curseForgeClient, + ModrinthClient modrinthClient, + GameService gameService, + DownloadService downloadService, + NotificationService notificaitonService) { - _resource = resource; - _navigationService = navigationService; + _curseForgeClient = curseForgeClient; + _modrinthClient = modrinthClient; + + _gameService = gameService; + _downloadService = downloadService; + _notificationService = notificaitonService; MinecraftInstance = _gameService.ActiveGame; } + void IDialogParameterAware.HandleParameter(object param) + { + (_resource, _) = ((object, INavigationService))param; + } + protected override void OnPropertyChanged(PropertyChangedEventArgs e) { base.OnPropertyChanged(e); diff --git a/Natsurainko.FluentLauncher/ViewModels/Common/UploadSkinDialogViewModel.cs b/Natsurainko.FluentLauncher/ViewModels/Common/UploadSkinDialogViewModel.cs index 6fc1d942..d51ffdd0 100644 --- a/Natsurainko.FluentLauncher/ViewModels/Common/UploadSkinDialogViewModel.cs +++ b/Natsurainko.FluentLauncher/ViewModels/Common/UploadSkinDialogViewModel.cs @@ -1,5 +1,6 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using FluentLauncher.Infra.UI.Dialogs; using Microsoft.UI.Xaml.Controls; using Microsoft.Win32; using Natsurainko.FluentLauncher.Services.Network; @@ -14,16 +15,18 @@ #nullable disable namespace Natsurainko.FluentLauncher.ViewModels.Common; -public partial class UploadSkinDialogViewModel : ObservableObject +public partial class UploadSkinDialogViewModel : ObservableObject, IDialogParameterAware { - private readonly Account _account; + private Account _account = null!; private ContentDialog _dialog; private readonly NotificationService _notificationService = App.GetService(); - public UploadSkinDialogViewModel(Account account) + public UploadSkinDialogViewModel() { } + + void IDialogParameterAware.HandleParameter(object param) { - _account = account; + _account = (Account)param; } [ObservableProperty] diff --git a/Natsurainko.FluentLauncher/ViewModels/Cores/Manage/ConfigViewModel.cs b/Natsurainko.FluentLauncher/ViewModels/Cores/Manage/ConfigViewModel.cs index 5ea09ff4..5dcd069c 100644 --- a/Natsurainko.FluentLauncher/ViewModels/Cores/Manage/ConfigViewModel.cs +++ b/Natsurainko.FluentLauncher/ViewModels/Cores/Manage/ConfigViewModel.cs @@ -1,6 +1,8 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using FluentLauncher.Infra.UI.Dialogs; using FluentLauncher.Infra.UI.Navigation; +using Microsoft.UI.Xaml.Controls; using Natsurainko.FluentLauncher.Models.Launch; using Natsurainko.FluentLauncher.Services.Accounts; using Natsurainko.FluentLauncher.Utils.Extensions; @@ -33,9 +35,12 @@ internal partial class ConfigViewModel : ObservableObject, INavigationAware [ObservableProperty] public partial Account TargetedAccount { get; set; } - public ConfigViewModel(AccountService accountService) + private readonly IDialogActivationService _dialogs; + + public ConfigViewModel(AccountService accountService, IDialogActivationService dialogs) { Accounts = accountService.Accounts; + _dialogs = dialogs; } void INavigationAware.OnNavigatedTo(object parameter) @@ -79,11 +84,7 @@ private void LoadTargetedAccount() [RelayCommand] public async Task AddArgument() { - await new AddVmArgumentDialog() - { - DataContext = new AddVmArgumentDialogViewModel(VmArguments.Add) - }.ShowAsync(); - + await _dialogs.ShowAsync("AddVmArgumentDialog", (object)VmArguments.Add); InstanceConfig.VmParameters = [.. VmArguments]; } diff --git a/Natsurainko.FluentLauncher/ViewModels/Cores/Manage/DefaultViewModel.cs b/Natsurainko.FluentLauncher/ViewModels/Cores/Manage/DefaultViewModel.cs index 27ed1255..5c91c9da 100644 --- a/Natsurainko.FluentLauncher/ViewModels/Cores/Manage/DefaultViewModel.cs +++ b/Natsurainko.FluentLauncher/ViewModels/Cores/Manage/DefaultViewModel.cs @@ -1,6 +1,8 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using FluentLauncher.Infra.UI.Dialogs; using FluentLauncher.Infra.UI.Navigation; +using Microsoft.UI.Xaml.Controls; using Natsurainko.FluentLauncher.Models.Launch; using Natsurainko.FluentLauncher.Services.Launch; using Natsurainko.FluentLauncher.Services.UI; @@ -24,6 +26,7 @@ internal partial class DefaultViewModel : ObservableObject, INavigationAware private readonly GameService _gameService; private readonly NotificationService _notificationService; private readonly QuickLaunchService _quickLaunchService; + private readonly IDialogActivationService _dialogs; private JumpList jumpList; @@ -36,12 +39,14 @@ public DefaultViewModel( GameService gameService, INavigationService navigationService, NotificationService notificationService, - QuickLaunchService quickLaunchService) + QuickLaunchService quickLaunchService, + IDialogActivationService dialogs) { _gameService = gameService; _navigationService = navigationService; _notificationService = notificationService; _quickLaunchService = quickLaunchService; + _dialogs = dialogs; } [ObservableProperty] @@ -102,10 +107,7 @@ private void InstanceConfig_PropertyChanged(object sender, PropertyChangedEventA public async Task OpenVersionFolder() => await Launcher.LaunchFolderPathAsync(MinecraftInstance.GetGameDirectory()); [RelayCommand] - public async Task DeleteGame() => await new DeleteInstanceDialog() - { - DataContext = new DeleteInstanceDialogViewModel(MinecraftInstance, _navigationService, _notificationService, _gameService) - }.ShowAsync(); + public async Task DeleteGame() => await _dialogs.ShowAsync("DeleteInstanceDialog", MinecraftInstance); protected override async void OnPropertyChanged(PropertyChangedEventArgs e) { diff --git a/Natsurainko.FluentLauncher/ViewModels/Downloads/DefaultViewModel.cs b/Natsurainko.FluentLauncher/ViewModels/Downloads/DefaultViewModel.cs index ea0a1168..0cce88be 100644 --- a/Natsurainko.FluentLauncher/ViewModels/Downloads/DefaultViewModel.cs +++ b/Natsurainko.FluentLauncher/ViewModels/Downloads/DefaultViewModel.cs @@ -1,6 +1,8 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using FluentLauncher.Infra.UI.Dialogs; using FluentLauncher.Infra.UI.Navigation; +using Microsoft.UI.Xaml.Controls; using Natsurainko.FluentLauncher.Models.UI; using Natsurainko.FluentLauncher.Services.Launch; using Natsurainko.FluentLauncher.Services.Network; @@ -28,6 +30,7 @@ internal partial class DefaultViewModel : ObservableObject, INavigationAware private readonly CacheInterfaceService _cacheInterfaceService; private readonly SearchProviderService _searchProviderService; private readonly NotificationService _notificationService; + private readonly IDialogActivationService _dialogs; private readonly CurseForgeClient _curseForgeClient; private readonly ModrinthClient _modrinthClient; @@ -39,13 +42,15 @@ public DefaultViewModel( SearchProviderService searchProviderService, NotificationService notificationService, CurseForgeClient curseForgeClient, - ModrinthClient modrinthClient) + ModrinthClient modrinthClient, + IDialogActivationService dialogs) { _navigationService = navigationService; _gameService = gameService; _cacheInterfaceService = cacheInterfaceService; _searchProviderService = searchProviderService; _notificationService = notificationService; + _dialogs = dialogs; _curseForgeClient = curseForgeClient; _modrinthClient = modrinthClient; @@ -231,7 +236,7 @@ void ParseModrinthTask(Task> task) void SearchMoreModrinth() => _navigationService.NavigateTo("Download/Search", new SearchOptions { ResourceSource = 2 }); [RelayCommand] - async Task DownloadResource(object resource) => await new DownloadResourceDialog() { DataContext = new DownloadResourceDialogViewModel(resource, _navigationService) }.ShowAsync(); + async Task DownloadResource(object resource) => await _dialogs.ShowAsync("DownloadResourceDialog", (resource, _navigationService)); [RelayCommand] void ResourceDetails(object resource) => _navigationService.NavigateTo("Download/Details", resource); diff --git a/Natsurainko.FluentLauncher/ViewModels/Downloads/DetailsViewModel.cs b/Natsurainko.FluentLauncher/ViewModels/Downloads/DetailsViewModel.cs index f1c62fc6..8b967982 100644 --- a/Natsurainko.FluentLauncher/ViewModels/Downloads/DetailsViewModel.cs +++ b/Natsurainko.FluentLauncher/ViewModels/Downloads/DetailsViewModel.cs @@ -1,6 +1,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.WinUI.UI.Controls; +using FluentLauncher.Infra.UI.Dialogs; using FluentLauncher.Infra.UI.Navigation; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -21,6 +22,7 @@ internal partial class DetailsViewModel : ObservableObject, INavigationAware private object _resource; private readonly INavigationService _navigationService; + private readonly IDialogActivationService _dialogs; private readonly CurseForgeClient _curseForgeClient; private readonly ModrinthClient _modrinthClient; @@ -28,11 +30,13 @@ internal partial class DetailsViewModel : ObservableObject, INavigationAware public DetailsViewModel( INavigationService navigationService, CurseForgeClient curseForgeClient, - ModrinthClient modrinthClient) + ModrinthClient modrinthClient, + IDialogActivationService dialogs) { _navigationService = navigationService; _curseForgeClient = curseForgeClient; _modrinthClient = modrinthClient; + _dialogs = dialogs; } [ObservableProperty] @@ -100,7 +104,7 @@ void ParseResource() } [RelayCommand] - async Task DownloadResource() => await new DownloadResourceDialog() { DataContext = new DownloadResourceDialogViewModel(_resource, _navigationService) }.ShowAsync(); + async Task DownloadResource() => await _dialogs.ShowAsync("DownloadResourceDialog", (_resource, _navigationService)); [RelayCommand] public async Task MarkdownTextBlockLoadedEvent(object args) diff --git a/Natsurainko.FluentLauncher/ViewModels/Downloads/SearchViewModel.cs b/Natsurainko.FluentLauncher/ViewModels/Downloads/SearchViewModel.cs index 1e8976f2..14bc7638 100644 --- a/Natsurainko.FluentLauncher/ViewModels/Downloads/SearchViewModel.cs +++ b/Natsurainko.FluentLauncher/ViewModels/Downloads/SearchViewModel.cs @@ -1,6 +1,8 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using FluentLauncher.Infra.UI.Dialogs; using FluentLauncher.Infra.UI.Navigation; +using Microsoft.UI.Xaml.Controls; using Natsurainko.FluentLauncher.Services.Launch; using Natsurainko.FluentLauncher.Services.Network; using Natsurainko.FluentLauncher.Services.UI; @@ -28,6 +30,7 @@ internal partial class SearchViewModel : ObservableObject, INavigationAware private readonly CacheInterfaceService _cacheInterfaceService; private readonly SearchProviderService _searchProviderService; private readonly NotificationService _notificationService; + private readonly IDialogActivationService _dialogs; private readonly CurseForgeClient _curseForgeClient; private readonly ModrinthClient _modrinthClient; @@ -39,13 +42,15 @@ public SearchViewModel( SearchProviderService searchProviderService, NotificationService notificationService, CurseForgeClient curseForgeClient, - ModrinthClient modrinthClient) + ModrinthClient modrinthClient, + IDialogActivationService dialogs) { _navigationService = navigationService; _gameService = gameService; _cacheInterfaceService = cacheInterfaceService; _searchProviderService = searchProviderService; _notificationService = notificationService; + _dialogs = dialogs; _curseForgeClient = curseForgeClient; _modrinthClient = modrinthClient; @@ -223,7 +228,7 @@ void QueryReceiver(string searchText) } [RelayCommand] - async Task DownloadResource(object resource) => await new DownloadResourceDialog() { DataContext = new DownloadResourceDialogViewModel(resource, _navigationService) }.ShowAsync(); + async Task DownloadResource(object resource) => await _dialogs.ShowAsync("DownloadResourceDialog", (resource, _navigationService)); [RelayCommand] void ResourceDetails(object resource) => _navigationService.NavigateTo("Download/Details", resource); diff --git a/Natsurainko.FluentLauncher/ViewModels/OOBE/OOBEViewModel.cs b/Natsurainko.FluentLauncher/ViewModels/OOBE/OOBEViewModel.cs index ad83a078..72b58f33 100644 --- a/Natsurainko.FluentLauncher/ViewModels/OOBE/OOBEViewModel.cs +++ b/Natsurainko.FluentLauncher/ViewModels/OOBE/OOBEViewModel.cs @@ -1,6 +1,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using FluentLauncher.Infra.Settings.Mvvm; +using FluentLauncher.Infra.UI.Dialogs; using FluentLauncher.Infra.UI.Navigation; using Microsoft.UI.Xaml.Controls; using Microsoft.Win32; @@ -35,6 +36,7 @@ internal partial class OOBEViewModel : ObservableObject, INavigationAware, ISett private readonly GameService _gameService; private readonly NotificationService _notificationService; private readonly AccountService _accountService; + private readonly IDialogActivationService _dialogs; #endregion @@ -43,13 +45,15 @@ public OOBEViewModel( SettingsService settings, GameService gameService, NotificationService notificationService, - AccountService accountService) + AccountService accountService, + IDialogActivationService dialogs) { _navigationService = navigationService; _settings = settings; _gameService = gameService; _notificationService = notificationService; _accountService = accountService; + _dialogs = dialogs; // Init accounts Accounts = accountService.Accounts; @@ -334,8 +338,7 @@ partial void OnActiveAccountChanged(Account value) } [RelayCommand] - public void Login(Button parameter) - => _ = new AuthenticationWizardDialog { XamlRoot = parameter.XamlRoot }.ShowAsync(); + public async Task Login(Button parameter) => await _dialogs.ShowAsync("AuthenticationWizardDialog"); [RelayCommand] public void RemoveAccount(Account account) diff --git a/Natsurainko.FluentLauncher/ViewModels/Settings/AccountViewModel.cs b/Natsurainko.FluentLauncher/ViewModels/Settings/AccountViewModel.cs index cc095b89..70896139 100644 --- a/Natsurainko.FluentLauncher/ViewModels/Settings/AccountViewModel.cs +++ b/Natsurainko.FluentLauncher/ViewModels/Settings/AccountViewModel.cs @@ -1,8 +1,10 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using FluentLauncher.Infra.Settings.Mvvm; +using FluentLauncher.Infra.UI.Dialogs; using FluentLauncher.Infra.UI.Navigation; using Microsoft.Extensions.DependencyInjection; +using Microsoft.UI.Xaml.Controls; using Natsurainko.FluentLauncher.Services.Accounts; using Natsurainko.FluentLauncher.Services.Network; using Natsurainko.FluentLauncher.Services.Settings; @@ -29,6 +31,7 @@ internal partial class AccountViewModel : SettingsViewModelBase, ISettingsViewMo private readonly NotificationService _notificationService; private readonly INavigationService _navigationService; private readonly CacheSkinService _cacheSkinService; + private readonly IDialogActivationService _dialogs; public AccountViewModel( SettingsService settingsService, @@ -36,7 +39,8 @@ public AccountViewModel( AuthenticationService authenticationService, NotificationService notificationService, INavigationService navigationService, - CacheSkinService cacheSkinService) + CacheSkinService cacheSkinService, + IDialogActivationService dialogs) { _settingsService = settingsService; _accountService = accountService; @@ -44,6 +48,7 @@ public AccountViewModel( _notificationService = notificationService; _navigationService = navigationService; _cacheSkinService = cacheSkinService; + _dialogs = dialogs; Accounts = accountService.Accounts; ActiveAccount = accountService.ActiveAccount; @@ -75,7 +80,7 @@ partial void OnActiveAccountChanged(Account value) } [RelayCommand] - public async Task Login() => await new AuthenticationWizardDialog().ShowAsync(); + public async Task Login() => await _dialogs.ShowAsync("AuthenticationWizardDialog"); [RelayCommand] public async Task Refresh() @@ -91,14 +96,7 @@ await _accountService.RefreshAccountAsync(ActiveAccount).ContinueWith(task => } [RelayCommand] - public void Switch() - { - var switchAccountDialog = new SwitchAccountDialog - { - DataContext = App.Services.GetService() - }; - _ = switchAccountDialog.ShowAsync(); - } + public async Task Switch() => await _dialogs.ShowAsync("SwitchAccountDialog"); [RelayCommand] public void OpenSkinFile() diff --git a/Natsurainko.FluentLauncher/ViewModels/Settings/SkinViewModel.cs b/Natsurainko.FluentLauncher/ViewModels/Settings/SkinViewModel.cs index 42861d2c..793d693b 100644 --- a/Natsurainko.FluentLauncher/ViewModels/Settings/SkinViewModel.cs +++ b/Natsurainko.FluentLauncher/ViewModels/Settings/SkinViewModel.cs @@ -2,8 +2,10 @@ using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging.Messages; using FluentLauncher.Infra.Settings.Mvvm; +using FluentLauncher.Infra.UI.Dialogs; using HelixToolkit.SharpDX.Core; using HelixToolkit.WinUI; +using Microsoft.UI.Xaml.Controls; using Natsurainko.FluentLauncher.Services.Accounts; using Natsurainko.FluentLauncher.Services.Network; using Natsurainko.FluentLauncher.Services.Settings; @@ -34,17 +36,20 @@ internal partial class SkinViewModel : SettingsViewModelBase, ISettingsViewModel private readonly AccountService _accountService; private readonly CacheSkinService _cacheSkinService; private readonly NotificationService _notificationService; + private readonly IDialogActivationService _dialogs; public SkinViewModel( SettingsService settingsService, AccountService accountService, CacheSkinService cacheSkinService, - NotificationService notificationService) + NotificationService notificationService, + IDialogActivationService dialogs) { _settingsService = settingsService; _accountService = accountService; _cacheSkinService = cacheSkinService; _notificationService = notificationService; + _dialogs = dialogs; ActiveAccount = accountService.ActiveAccount!; @@ -155,8 +160,8 @@ public async Task IsSlimSkin() [RelayCommand] public async Task UploadSkin() { - await new UploadSkinDialog() { DataContext = new UploadSkinDialogViewModel(ActiveAccount) }.ShowAsync(); - _ = Task.Run(LoadModel); + await _dialogs.ShowAsync("UploadSkinDialog", ActiveAccount); + await LoadModel(); } [RelayCommand] diff --git a/Natsurainko.FluentLauncher/Views/Common/AddVmArgumentDialog.xaml.cs b/Natsurainko.FluentLauncher/Views/Common/AddVmArgumentDialog.xaml.cs index 8c8703e2..9c6157c4 100644 --- a/Natsurainko.FluentLauncher/Views/Common/AddVmArgumentDialog.xaml.cs +++ b/Natsurainko.FluentLauncher/Views/Common/AddVmArgumentDialog.xaml.cs @@ -6,8 +6,6 @@ public sealed partial class AddVmArgumentDialog : ContentDialog { public AddVmArgumentDialog() { - this.XamlRoot = MainWindow.XamlRoot; - - this.InitializeComponent(); + InitializeComponent(); } } diff --git a/Natsurainko.FluentLauncher/Views/Common/AuthenticationWizardDialog.xaml.cs b/Natsurainko.FluentLauncher/Views/Common/AuthenticationWizardDialog.xaml.cs index f47cb797..ee2a8767 100644 --- a/Natsurainko.FluentLauncher/Views/Common/AuthenticationWizardDialog.xaml.cs +++ b/Natsurainko.FluentLauncher/Views/Common/AuthenticationWizardDialog.xaml.cs @@ -9,12 +9,6 @@ public sealed partial class AuthenticationWizardDialog : ContentDialog { public AuthenticationWizardDialog() { - this.XamlRoot = MainWindow.XamlRoot; - - this.InitializeComponent(); - DataContext = new AuthenticationWizardDialogViewModel( - App.GetService(), - App.GetService(), - App.GetService()); + InitializeComponent(); } } diff --git a/Natsurainko.FluentLauncher/Views/Common/DeleteInstanceDialog.xaml.cs b/Natsurainko.FluentLauncher/Views/Common/DeleteInstanceDialog.xaml.cs index dca30936..a19ebfaf 100644 --- a/Natsurainko.FluentLauncher/Views/Common/DeleteInstanceDialog.xaml.cs +++ b/Natsurainko.FluentLauncher/Views/Common/DeleteInstanceDialog.xaml.cs @@ -6,8 +6,6 @@ public sealed partial class DeleteInstanceDialog : ContentDialog { public DeleteInstanceDialog() { - this.XamlRoot = MainWindow.XamlRoot; - - this.InitializeComponent(); + InitializeComponent(); } } diff --git a/Natsurainko.FluentLauncher/Views/Common/DownloadResourceDialog.xaml.cs b/Natsurainko.FluentLauncher/Views/Common/DownloadResourceDialog.xaml.cs index d1d3049f..314d18ab 100644 --- a/Natsurainko.FluentLauncher/Views/Common/DownloadResourceDialog.xaml.cs +++ b/Natsurainko.FluentLauncher/Views/Common/DownloadResourceDialog.xaml.cs @@ -7,9 +7,7 @@ public sealed partial class DownloadResourceDialog : ContentDialog { public DownloadResourceDialog() { - this.XamlRoot = MainWindow.XamlRoot; - - this.InitializeComponent(); + InitializeComponent(); } private void AutoSuggestBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) diff --git a/Natsurainko.FluentLauncher/Views/Common/ExceptionDialog.xaml.cs b/Natsurainko.FluentLauncher/Views/Common/ExceptionDialog.xaml.cs index dd00ad2d..528820c7 100644 --- a/Natsurainko.FluentLauncher/Views/Common/ExceptionDialog.xaml.cs +++ b/Natsurainko.FluentLauncher/Views/Common/ExceptionDialog.xaml.cs @@ -3,12 +3,11 @@ namespace Natsurainko.FluentLauncher.Views.Common; +// This dialog is not managed by the DI framework because it is called in App.xaml.cs, where a scope is not available public sealed partial class ExceptionDialog : ContentDialog { public ExceptionDialog(string errorMessage = "") { - this.XamlRoot = MainWindow.XamlRoot; - InitializeComponent(); DataContext = new ExceptionDialogViewModel(errorMessage); } diff --git a/Natsurainko.FluentLauncher/Views/Common/SwitchAccountDialog.xaml.cs b/Natsurainko.FluentLauncher/Views/Common/SwitchAccountDialog.xaml.cs index a43c9e0c..4b8c2802 100644 --- a/Natsurainko.FluentLauncher/Views/Common/SwitchAccountDialog.xaml.cs +++ b/Natsurainko.FluentLauncher/Views/Common/SwitchAccountDialog.xaml.cs @@ -6,8 +6,7 @@ public sealed partial class SwitchAccountDialog : ContentDialog { public SwitchAccountDialog() { - this.XamlRoot = MainWindow.XamlRoot; - this.InitializeComponent(); + InitializeComponent(); } private void ContentDialog_Unloaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) diff --git a/Natsurainko.FluentLauncher/Views/Common/UploadSkinDialog.xaml.cs b/Natsurainko.FluentLauncher/Views/Common/UploadSkinDialog.xaml.cs index 5e91c405..71d19f16 100644 --- a/Natsurainko.FluentLauncher/Views/Common/UploadSkinDialog.xaml.cs +++ b/Natsurainko.FluentLauncher/Views/Common/UploadSkinDialog.xaml.cs @@ -7,9 +7,7 @@ public sealed partial class UploadSkinDialog : ContentDialog { public UploadSkinDialog() { - this.XamlRoot = MainWindow.XamlRoot; - - this.InitializeComponent(); + InitializeComponent(); } private void AutoSuggestBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) diff --git a/infra/FluentLauncher.Infra.UI/Dialogs/IDialogActivationService.cs b/infra/FluentLauncher.Infra.UI/Dialogs/IDialogActivationService.cs index e32b5d32..83be5b2c 100644 --- a/infra/FluentLauncher.Infra.UI/Dialogs/IDialogActivationService.cs +++ b/infra/FluentLauncher.Infra.UI/Dialogs/IDialogActivationService.cs @@ -12,12 +12,12 @@ public interface IDialogActivationService /// Shows a dialog with the given dialog ID. /// /// The key of the dialog to show. - Task ShowDialogAsync(string key); + Task ShowAsync(string key); /// /// Shows a dialog with the given dialog ID. /// /// The key of the dialog to show. /// Parameter passed to the dialog. - Task ShowDialogAsync(string key, object param); + Task ShowAsync(string key, object param); } diff --git a/infra/FluentLauncher.Infra.UI/Dialogs/IDialogParameterAware.cs b/infra/FluentLauncher.Infra.UI/Dialogs/IDialogParameterAware.cs index 9eb2822e..2b87d4c5 100644 --- a/infra/FluentLauncher.Infra.UI/Dialogs/IDialogParameterAware.cs +++ b/infra/FluentLauncher.Infra.UI/Dialogs/IDialogParameterAware.cs @@ -3,7 +3,7 @@ /// /// Interface for a dialog view model that can receive a parameter. /// -interface IDialogParameterAware +public interface IDialogParameterAware { /// /// Sets the parameter for the dialog view model. diff --git a/infra/FluentLauncher.Infra.WinUI/AppHost/WinUIApplicationBuilder.cs b/infra/FluentLauncher.Infra.WinUI/AppHost/WinUIApplicationBuilder.cs index 3119c11c..35dbec8a 100644 --- a/infra/FluentLauncher.Infra.WinUI/AppHost/WinUIApplicationBuilder.cs +++ b/infra/FluentLauncher.Infra.WinUI/AppHost/WinUIApplicationBuilder.cs @@ -1,7 +1,9 @@ using FluentLauncher.Infra.UI; +using FluentLauncher.Infra.UI.Dialogs; using FluentLauncher.Infra.UI.Navigation; using FluentLauncher.Infra.UI.Pages; using FluentLauncher.Infra.UI.Windows; +using FluentLauncher.Infra.WinUI.Dialogs; using FluentLauncher.Infra.WinUI.Navigation; using FluentLauncher.Infra.WinUI.Pages; using FluentLauncher.Infra.WinUI.Windows; @@ -11,6 +13,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; using System; using System.Collections.Generic; @@ -27,6 +30,8 @@ public class WinUIApplicationBuilder : IHostApplicationBuilder public WinUIPageProviderBuilder Pages { get; } = new(); + public WinUIDialogProviderBuilder Dialogs { get; } = new(); + public WinUIApplicationBuilder(Func createApplicationFunc) { _createApplicationFunc = createApplicationFunc; @@ -64,11 +69,30 @@ private void ConfigureExtendedWinUIServices() { IServiceScope? parentScope = sp.GetRequiredService().ParentScope; if (parentScope is null) - return new WinUIWindowService(); // root scope + return new WinUIWindowService(); else return parentScope.GetRootScope().ServiceProvider.GetRequiredService(); }); + // Configure IDialogProvider + foreach (var (key, descriptor) in Dialogs.RegisteredDialogs) + { + Services.AddTransient(descriptor.DialogType); + if (descriptor.ViewModelType is not null) + Services.AddTransient(descriptor.ViewModelType); + } + Services.AddSingleton(Dialogs.Build); + + // Configure IDialogActivationService + Services.AddScoped>(sp => + { + IServiceScope? parentScope = sp.GetRequiredService().ParentScope; + if (parentScope is null) + return new WinUIDialogActivationService(sp.GetRequiredService(), sp.GetRequiredService()); + else + return parentScope.GetRootScope().ServiceProvider.GetRequiredService>(); + }); + // Configure INavigationService Services.AddScoped(); diff --git a/infra/FluentLauncher.Infra.WinUI/Dialogs/WinUIDialogActivationService.cs b/infra/FluentLauncher.Infra.WinUI/Dialogs/WinUIDialogActivationService.cs index 8ccff313..1ea66589 100644 --- a/infra/FluentLauncher.Infra.WinUI/Dialogs/WinUIDialogActivationService.cs +++ b/infra/FluentLauncher.Infra.WinUI/Dialogs/WinUIDialogActivationService.cs @@ -1,4 +1,7 @@ using FluentLauncher.Infra.UI.Dialogs; +using FluentLauncher.Infra.UI.Windows; +using FluentLauncher.Infra.WinUI.Windows; +using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using System; using System.Collections.Generic; @@ -10,13 +13,28 @@ namespace FluentLauncher.Infra.WinUI.Dialogs; class WinUIDialogActivationService : IDialogActivationService { - public Task ShowDialogAsync(string key) + private readonly IDialogProvider _dialogProvider; + private readonly XamlRoot _xamlRoot; + + public WinUIDialogActivationService(IDialogProvider dialogProvider, IWindowService windowService) + { + _dialogProvider = dialogProvider; + _xamlRoot = ((WinUIWindowService)windowService).Window.Content.XamlRoot; + } + + public Task ShowAsync(string key) { - throw new NotImplementedException(); + var dialog = (ContentDialog)_dialogProvider.GetDialog(key); + dialog.XamlRoot = _xamlRoot; + return dialog.ShowAsync().AsTask(); } - public Task ShowDialogAsync(string key, object param) + public Task ShowAsync(string key, object param) { - throw new NotImplementedException(); + var dialog = (ContentDialog)_dialogProvider.GetDialog(key); + dialog.XamlRoot = _xamlRoot; + if (dialog.DataContext is IDialogParameterAware vm) + vm.HandleParameter(param); + return dialog.ShowAsync().AsTask(); } } diff --git a/infra/FluentLauncher.Infra.WinUI/Windows/WinUIWindowService.cs b/infra/FluentLauncher.Infra.WinUI/Windows/WinUIWindowService.cs index 88a5222a..ac2cf049 100644 --- a/infra/FluentLauncher.Infra.WinUI/Windows/WinUIWindowService.cs +++ b/infra/FluentLauncher.Infra.WinUI/Windows/WinUIWindowService.cs @@ -9,24 +9,24 @@ namespace FluentLauncher.Infra.WinUI.Windows; /// public class WinUIWindowService : IWindowService { - private Window _window = null!; + public Window Window { get; private set; } = null!; public string Title { - get => _window.Title; - set => _window.Title = value; + get => Window.Title; + set => Window.Title = value; } public WinUIWindowService() { } - public void Close() => _window.Close(); + public void Close() => Window.Close(); - public void Minimize() => _window.Hide(); + public void Minimize() => Window.Hide(); - public void Activate() => _window.Activate(); + public void Activate() => Window.Activate(); public void InitializeService(Window window) { - _window = window; + Window = window; } }