using System; using System.Collections.ObjectModel; using System.Linq; using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.DryIoc.Factories; using Artemis.UI.Screens.Plugins; using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services.Builders; using Avalonia.ReactiveUI; using DynamicData; using DynamicData.Binding; using ReactiveUI; namespace Artemis.UI.Screens.Settings; public class PluginsTabViewModel : RoutableScreen { private readonly INotificationService _notificationService; private readonly IPluginManagementService _pluginManagementService; private readonly IWindowService _windowService; private string? _searchPluginInput; public PluginsTabViewModel(IPluginManagementService pluginManagementService, INotificationService notificationService, IWindowService windowService, ISettingsVmFactory settingsVmFactory) { _pluginManagementService = pluginManagementService; _notificationService = notificationService; _windowService = windowService; DisplayName = "Plugins"; SourceList plugins = new(); IObservable> pluginFilter = this.WhenAnyValue(vm => vm.SearchPluginInput).Throttle(TimeSpan.FromMilliseconds(100)).Select(CreatePredicate); plugins.Connect() .Filter(pluginFilter) .Sort(SortExpressionComparer.Ascending(p => p.Info.Name)) .Transform(settingsVmFactory.PluginSettingsViewModel) .ObserveOn(AvaloniaScheduler.Instance) .Bind(out ReadOnlyObservableCollection pluginViewModels) .Subscribe(); Plugins = pluginViewModels; this.WhenActivated(d => { plugins.AddRange(_pluginManagementService.GetAllPlugins()); Observable.FromEventPattern(x => _pluginManagementService.PluginLoaded += x, x => _pluginManagementService.PluginLoaded -= x) .Subscribe(a => plugins.Add(a.EventArgs.Plugin)) .DisposeWith(d); Observable.FromEventPattern(x => _pluginManagementService.PluginUnloaded += x, x => _pluginManagementService.PluginUnloaded -= x) .Subscribe(a => plugins.Remove(a.EventArgs.Plugin)) .DisposeWith(d); Disposable.Create(() => plugins.Clear()).DisposeWith(d); }); ImportPlugin = ReactiveCommand.CreateFromTask(ExecuteImportPlugin); } public ReadOnlyObservableCollection Plugins { get; } public ReactiveCommand ImportPlugin { get; } public string? SearchPluginInput { get => _searchPluginInput; set => RaiseAndSetIfChanged(ref _searchPluginInput, value); } public void OpenUrl(string url) { Utilities.OpenUrl(url); } private async Task ExecuteImportPlugin() { string[]? files = await _windowService.CreateOpenFileDialog().WithTitle("Import Artemis plugin").HavingFilter(f => f.WithExtension("zip").WithName("ZIP files")).ShowAsync(); if (files == null) return; try { Plugin? plugin = _pluginManagementService.ImportPlugin(files[0]); if (plugin == null) { await _windowService.ShowConfirmContentDialog("Failed to import plugin", "Make sure it is compatible with the current platform.", "Close", null); return; } SearchPluginInput = plugin.Info.Name; // Wait for the VM to be created asynchronously (it would be better to respond to some event here) await Task.Delay(200); // Enable it via the VM to enable the prerequisite dialog PluginSettingsViewModel? settingsViewModel = Plugins.FirstOrDefault(i => i.PluginViewModel.Plugin == plugin); if (settingsViewModel != null && !settingsViewModel.PluginViewModel.IsEnabled) await settingsViewModel.PluginViewModel.UpdateEnabled(true); _notificationService.CreateNotification() .WithTitle("Plugin imported") .WithMessage($"Added the '{plugin.Info.Name}' plugin") .WithSeverity(NotificationSeverity.Success) .Show(); } catch (Exception e) { await _windowService.ShowConfirmContentDialog("Failed to import plugin", "Make sure the selected ZIP file is a valid Artemis plugin.\r\n" + e.Message, "Close", null); } } private Func CreatePredicate(string? text) { if (string.IsNullOrWhiteSpace(text)) return _ => true; return data => data.Info.Name.Contains(text, StringComparison.InvariantCultureIgnoreCase) || (data.Info.Description != null && data.Info.Description.Contains(text, StringComparison.InvariantCultureIgnoreCase)); } }