Flow.Launcher/Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginsSettingsViewModel.cs
Shengkai Lin 5e1ec2b3e9
Some checks failed
Build / build (push) Has been cancelled
fix(avalonia): update PluginManager API calls for Release build
- Replace PluginManager.AllPlugins with GetAllLoadedPlugins()
- Replace PluginManager.NonGlobalPlugins with GetNonGlobalPlugins()
- Fix InitializePluginsAsync to pass IResultUpdateRegister parameter
- Fix QueryBuilder.Build to pass required 3 parameters
- Implement IResultUpdateRegister interface on MainViewModel
2026-02-01 18:01:46 +08:00

497 lines
16 KiB
C#

using System;
using Avalonia;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Avalonia.Resource;
using Flow.Launcher.Avalonia.Views.Controls;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using FluentAvalonia.UI.Controls;
namespace Flow.Launcher.Avalonia.ViewModel.SettingPages;
public partial class PluginsSettingsViewModel : ObservableObject
{
private readonly Settings _settings;
private readonly Internationalization _i18n;
public PluginsSettingsViewModel()
{
_settings = Ioc.Default.GetRequiredService<Settings>();
_i18n = Ioc.Default.GetRequiredService<Internationalization>();
LoadDisplayModes();
LoadPlugins();
_settings.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(Settings.Language))
{
foreach (var item in DisplayModes)
{
item.UpdateLabels();
}
}
};
}
[ObservableProperty]
private ObservableCollection<PluginItemViewModel> _plugins = new();
[ObservableProperty]
private string _searchText = string.Empty;
public IEnumerable<PluginItemViewModel> FilteredPlugins =>
string.IsNullOrWhiteSpace(SearchText)
? Plugins
: Plugins.Where(p =>
p.Name.Contains(SearchText, StringComparison.OrdinalIgnoreCase) ||
p.Description.Contains(SearchText, StringComparison.OrdinalIgnoreCase) ||
p.ActionKeywordsText.Contains(SearchText, StringComparison.OrdinalIgnoreCase)
);
partial void OnSearchTextChanged(string value) => OnPropertyChanged(nameof(FilteredPlugins));
private void LoadPlugins()
{
var allPlugins = PluginManager.GetAllLoadedPlugins();
foreach (var plugin in allPlugins.OrderBy(p => p.Metadata.Disabled).ThenBy(p => p.Metadata.Name))
{
Plugins.Add(new PluginItemViewModel(plugin, _settings));
}
}
#region Display Mode
public enum DisplayMode
{
OnOff,
Priority,
SearchDelay,
HomeOnOff
}
public partial class DisplayModeItem : ObservableObject
{
private readonly Internationalization _i18n;
public DisplayMode Value { get; }
[ObservableProperty]
private string _display;
public DisplayModeItem(DisplayMode value, Internationalization i18n)
{
Value = value;
_i18n = i18n;
UpdateLabels();
}
public void UpdateLabels()
{
Display = Value switch
{
DisplayMode.OnOff => _i18n.GetTranslation("DisplayModeOnOff"),
DisplayMode.Priority => _i18n.GetTranslation("DisplayModePriority"),
DisplayMode.SearchDelay => _i18n.GetTranslation("DisplayModeSearchDelay"),
DisplayMode.HomeOnOff => _i18n.GetTranslation("DisplayModeHomeOnOff"),
_ => Value.ToString()
};
}
}
[ObservableProperty]
private List<DisplayModeItem> _displayModes = new();
[ObservableProperty]
private DisplayModeItem? _selectedDisplayModeItem;
partial void OnSelectedDisplayModeItemChanged(DisplayModeItem? value)
{
if (value != null)
UpdateDisplayModeFlags(value.Value);
}
[ObservableProperty]
private bool _isOnOffSelected = true;
[ObservableProperty]
private bool _isPrioritySelected;
[ObservableProperty]
private bool _isSearchDelaySelected;
[ObservableProperty]
private bool _isHomeOnOffSelected;
private void LoadDisplayModes()
{
DisplayModes = new List<DisplayModeItem>
{
new(DisplayMode.OnOff, _i18n),
new(DisplayMode.Priority, _i18n),
new(DisplayMode.SearchDelay, _i18n),
new(DisplayMode.HomeOnOff, _i18n)
};
// Set default
SelectedDisplayModeItem = DisplayModes[0];
}
private void UpdateDisplayModeFlags(DisplayMode mode)
{
IsOnOffSelected = mode == DisplayMode.OnOff;
IsPrioritySelected = mode == DisplayMode.Priority;
IsSearchDelaySelected = mode == DisplayMode.SearchDelay;
IsHomeOnOffSelected = mode == DisplayMode.HomeOnOff;
}
#endregion
[RelayCommand]
private async Task OpenHelper(Control source)
{
var helpDialog = new ContentDialog
{
Title = _i18n.GetTranslation("flowlauncher_settings"),
Content = new StackPanel
{
Spacing = 10,
Children =
{
new TextBlock
{
Text = _i18n.GetTranslation("priority"),
FontSize = 18,
FontWeight = FontWeight.Bold,
TextWrapping = TextWrapping.Wrap
},
new TextBlock
{
Text = _i18n.GetTranslation("priority_tips"),
TextWrapping = TextWrapping.Wrap
},
new TextBlock
{
Text = _i18n.GetTranslation("searchDelay"),
FontSize = 18,
FontWeight = FontWeight.Bold,
Margin = new Thickness(0, 10, 0, 0),
TextWrapping = TextWrapping.Wrap
},
new TextBlock
{
Text = _i18n.GetTranslation("searchDelayTimeTips"),
TextWrapping = TextWrapping.Wrap
},
new TextBlock
{
Text = _i18n.GetTranslation("homeTitle"),
FontSize = 18,
FontWeight = FontWeight.Bold,
Margin = new Thickness(0, 10, 0, 0),
TextWrapping = TextWrapping.Wrap
},
new TextBlock
{
Text = _i18n.GetTranslation("homeTips"),
TextWrapping = TextWrapping.Wrap
}
}
},
PrimaryButtonText = _i18n.GetTranslation("commonOK"),
CloseButtonText = null
};
await helpDialog.ShowAsync();
}
}
public partial class PluginItemViewModel : ObservableObject
{
private readonly PluginPair _plugin;
private readonly Settings _settings;
private readonly ISettingProvider? _settingProvider;
private readonly Internationalization _i18n;
public PluginItemViewModel(PluginPair plugin, Settings settings)
{
_plugin = plugin;
_settings = settings;
_i18n = Ioc.Default.GetRequiredService<Internationalization>();
PluginSettingsObject = _settings.PluginSettings.GetPluginSettings(plugin.Metadata.ID);
// Initialize settings provider
if (plugin.Plugin is ISettingProvider settingProvider)
{
if (plugin.Plugin is JsonRPCPluginBase jsonRpcPlugin)
{
if (jsonRpcPlugin.NeedCreateSettingPanel())
{
_settingProvider = settingProvider;
HasSettings = true;
}
}
else
{
_settingProvider = settingProvider;
HasSettings = true;
}
}
// Initialize Avalonia settings if available
if (HasSettings && _settingProvider != null)
{
try
{
AvaloniaSettingControl = _settingProvider.CreateSettingPanelAvalonia();
HasNativeAvaloniaSettings = AvaloniaSettingControl != null;
}
catch (Exception ex)
{
Flow.Launcher.Infrastructure.Logger.Log.Exception(nameof(PluginItemViewModel), $"Failed to create Avalonia settings for {Name}", ex);
}
}
// Listen to metadata changes
_plugin.Metadata.PropertyChanged += (_, args) =>
{
if (args.PropertyName == nameof(PluginMetadata.AvgQueryTime))
OnPropertyChanged(nameof(QueryTime));
if (args.PropertyName == nameof(PluginMetadata.ActionKeywords))
OnPropertyChanged(nameof(ActionKeywordsText));
};
_ = LoadIconAsync();
}
public Infrastructure.UserSettings.Plugin PluginSettingsObject { get; }
private async Task LoadIconAsync()
{
Icon = await Flow.Launcher.Avalonia.Helper.ImageLoader.LoadAsync(_plugin.Metadata.IcoPath);
}
[ObservableProperty]
private IImage? _icon;
[ObservableProperty]
private bool _hasSettings;
[ObservableProperty]
private bool _hasNativeAvaloniaSettings;
/// <summary>
/// True if plugin has settings but only WPF settings (no native Avalonia)
/// </summary>
public bool HasWpfOnlySettings => HasSettings && !HasNativeAvaloniaSettings;
[ObservableProperty]
private Control? _avaloniaSettingControl;
[ObservableProperty]
private bool _isExpanded;
public string Name => _plugin.Metadata.Name;
public string Description => _plugin.Metadata.Description;
public string Author => _plugin.Metadata.Author;
public string Version => _plugin.Metadata.Version;
public string IconPath => _plugin.Metadata.IcoPath;
public string ID => _plugin.Metadata.ID;
public string ActionKeywordsText => string.Join(Query.ActionKeywordSeparator, _plugin.Metadata.ActionKeywords);
public string InitTime => $"{_plugin.Metadata.InitTime}ms";
public string QueryTime => $"{_plugin.Metadata.AvgQueryTime}ms";
public bool IsDisabled
{
get => _plugin.Metadata.Disabled;
set
{
if (_plugin.Metadata.Disabled != value)
{
_plugin.Metadata.Disabled = value;
PluginSettingsObject.Disabled = value;
OnPropertyChanged();
// Also update the inverse property for binding convenience
OnPropertyChanged(nameof(PluginState));
}
}
}
public bool PluginState
{
get => !IsDisabled;
set => IsDisabled = !value;
}
public bool PluginHomeState
{
get => !_plugin.Metadata.HomeDisabled;
set
{
if (_plugin.Metadata.HomeDisabled != !value)
{
_plugin.Metadata.HomeDisabled = !value;
PluginSettingsObject.HomeDisabled = !value;
OnPropertyChanged();
}
}
}
public int Priority
{
get => _plugin.Metadata.Priority;
set
{
if (_plugin.Metadata.Priority != value)
{
_plugin.Metadata.Priority = value;
PluginSettingsObject.Priority = value;
OnPropertyChanged();
}
}
}
public double PluginSearchDelayTime
{
get => _plugin.Metadata.SearchDelayTime == null ? double.NaN : _plugin.Metadata.SearchDelayTime.Value;
set
{
if (double.IsNaN(value))
{
_plugin.Metadata.SearchDelayTime = null;
PluginSettingsObject.SearchDelayTime = null;
}
else
{
_plugin.Metadata.SearchDelayTime = (int)value;
PluginSettingsObject.SearchDelayTime = (int)value;
}
OnPropertyChanged();
OnPropertyChanged(nameof(SearchDelayTimeText));
}
}
public string SearchDelayTimeText => _plugin.Metadata.SearchDelayTime == null ?
_i18n.GetTranslation("default") :
_i18n.GetTranslation($"SearchDelayTime{_plugin.Metadata.SearchDelayTime}");
public bool SearchDelayEnabled => _settings.SearchQueryResultsWithDelay;
public string DefaultSearchDelay => _settings.SearchDelayTime.ToString();
public bool HomeEnabled => _settings.ShowHomePage && PluginManager.IsHomePlugin(_plugin.Metadata.ID);
[RelayCommand]
private void OpenSettings()
{
if (_settingProvider == null) return;
if (HasNativeAvaloniaSettings)
{
IsExpanded = !IsExpanded;
return;
}
try
{
// Create the WPF settings panel and show in a standalone WPF window
var settingsControl = _settingProvider.CreateSettingPanel();
if (settingsControl != null)
{
WpfSettingsWindow.Show(settingsControl, Name);
}
}
catch (Exception ex)
{
Flow.Launcher.Infrastructure.Logger.Log.Exception(nameof(PluginItemViewModel), $"Failed to open settings for {Name}", ex);
}
}
[RelayCommand]
private void OpenPluginDirectory()
{
var directory = _plugin.Metadata.PluginDirectory;
if (!string.IsNullOrEmpty(directory))
App.API.OpenDirectory(directory);
}
[RelayCommand]
private void OpenSourceCodeLink()
{
if (!string.IsNullOrEmpty(_plugin.Metadata.Website))
App.API.OpenUrl(_plugin.Metadata.Website);
}
[RelayCommand]
private async Task OpenDeletePluginWindow()
{
// We need to implement a dialog for confirmation
var dialog = new ContentDialog
{
Title = _i18n.GetTranslation("plugin_uninstall_title"),
Content = string.Format(_i18n.GetTranslation("plugin_uninstall_content"), Name),
PrimaryButtonText = _i18n.GetTranslation("yes"),
CloseButtonText = _i18n.GetTranslation("no")
};
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
await PluginInstaller.UninstallPluginAndCheckRestartAsync(_plugin.Metadata);
}
}
[RelayCommand]
private async Task SetActionKeywords()
{
// Simple dialog to edit keywords
var textBox = new TextBox
{
Text = ActionKeywordsText,
AcceptsReturn = false
};
var dialog = new ContentDialog
{
Title = _i18n.GetTranslation("actionKeywords"),
Content = new StackPanel
{
Spacing = 10,
Children =
{
new TextBlock { Text = _i18n.GetTranslation("actionKeywordsDescription") },
textBox
}
},
PrimaryButtonText = _i18n.GetTranslation("done"),
CloseButtonText = _i18n.GetTranslation("cancel")
};
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
var newKeywords = textBox.Text?.Split(Query.ActionKeywordSeparator, StringSplitOptions.RemoveEmptyEntries).Select(k => k.Trim()).ToList();
if (newKeywords != null)
{
// Validate?
// For now just update
_plugin.Metadata.ActionKeywords = newKeywords;
PluginSettingsObject.ActionKeywords = newKeywords;
OnPropertyChanged(nameof(ActionKeywordsText));
}
}
}
}