mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Wire up Avalonia UI to actual plugin system
- Load settings from disk via FlowLauncherJsonStorage - Initialize PluginManager and query plugins on text change - Add minimal AvaloniaPublicAPI implementing IPublicAPI - Execute plugin results on Enter key - Add WPF framework reference for IPublicAPI compatibility
This commit is contained in:
parent
4120407ac3
commit
e961dc5668
7 changed files with 315 additions and 135 deletions
|
|
@ -1,52 +1,93 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Flow.Launcher.Avalonia.ViewModel;
|
||||
using Flow.Launcher.Core.Plugin;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.Storage;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Flow.Launcher.Avalonia;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
private static readonly string ClassName = nameof(App);
|
||||
private Settings? _settings;
|
||||
private MainViewModel? _mainVM;
|
||||
|
||||
public static IPublicAPI? API { get; private set; }
|
||||
|
||||
public override void Initialize() => AvaloniaXamlLoader.Load(this);
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
// Set up dependency injection
|
||||
var services = new ServiceCollection();
|
||||
ConfigureServices(services);
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
Ioc.Default.ConfigureServices(serviceProvider);
|
||||
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindow();
|
||||
}
|
||||
LoadSettings();
|
||||
ConfigureDI();
|
||||
|
||||
API = Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||
_mainVM = Ioc.Default.GetRequiredService<MainViewModel>();
|
||||
|
||||
desktop.MainWindow = new MainWindow();
|
||||
|
||||
Dispatcher.UIThread.Post(async () => await InitializePluginsAsync(), DispatcherPriority.Background);
|
||||
}
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
|
||||
private void ConfigureServices(IServiceCollection services)
|
||||
private void LoadSettings()
|
||||
{
|
||||
// Register settings - for now create a default instance
|
||||
// In production, this would load from the existing settings file
|
||||
services.AddSingleton<Settings>(_ =>
|
||||
try
|
||||
{
|
||||
var settings = new Settings();
|
||||
// Set some defaults for the Avalonia version
|
||||
settings.WindowSize = 580;
|
||||
settings.WindowHeightSize = 42;
|
||||
settings.QueryBoxFontSize = 24;
|
||||
settings.ItemHeightSize = 50;
|
||||
settings.ResultItemFontSize = 14;
|
||||
settings.ResultSubItemFontSize = 12;
|
||||
settings.MaxResultsToShow = 6;
|
||||
return settings;
|
||||
});
|
||||
var storage = new FlowLauncherJsonStorage<Settings>();
|
||||
_settings = storage.Load();
|
||||
_settings.SetStorage(storage);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception(ClassName, "Settings load failed", e);
|
||||
_settings = new Settings
|
||||
{
|
||||
WindowSize = 580, WindowHeightSize = 42, QueryBoxFontSize = 24,
|
||||
ItemHeightSize = 50, ResultItemFontSize = 14, ResultSubItemFontSize = 12, MaxResultsToShow = 6
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureDI()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton(_settings!);
|
||||
services.AddSingleton<IAlphabet, PinyinAlphabet>();
|
||||
services.AddSingleton<StringMatcher>();
|
||||
services.AddSingleton<MainViewModel>();
|
||||
services.AddSingleton<IPublicAPI>(sp => new AvaloniaPublicAPI(
|
||||
sp.GetRequiredService<Settings>(),
|
||||
() => sp.GetRequiredService<MainViewModel>()));
|
||||
Ioc.Default.ConfigureServices(services.BuildServiceProvider());
|
||||
}
|
||||
|
||||
private async Task InitializePluginsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Info(ClassName, "Loading plugins...");
|
||||
PluginManager.LoadPlugins(_settings!.PluginSettings);
|
||||
Log.Info(ClassName, $"Loaded {PluginManager.AllPlugins.Count} plugins");
|
||||
|
||||
await PluginManager.InitializePluginsAsync();
|
||||
Log.Info(ClassName, "Plugins initialized");
|
||||
|
||||
_mainVM?.OnPluginsReady();
|
||||
}
|
||||
catch (Exception e) { Log.Exception(ClassName, "Plugin init failed", e); }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
141
Flow.Launcher.Avalonia/AvaloniaPublicAPI.cs
Normal file
141
Flow.Launcher.Avalonia/AvaloniaPublicAPI.cs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
using Flow.Launcher.Core.Plugin;
|
||||
using Flow.Launcher.Avalonia.ViewModel;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
|
||||
namespace Flow.Launcher.Avalonia;
|
||||
|
||||
/// <summary>
|
||||
/// Minimal IPublicAPI for Avalonia - just enough for plugin queries to work.
|
||||
/// </summary>
|
||||
public class AvaloniaPublicAPI : IPublicAPI
|
||||
{
|
||||
private readonly Settings _settings;
|
||||
private readonly Func<MainViewModel> _getMainViewModel;
|
||||
|
||||
public AvaloniaPublicAPI(Settings settings, Func<MainViewModel> getMainViewModel)
|
||||
{
|
||||
_settings = settings;
|
||||
_getMainViewModel = getMainViewModel;
|
||||
}
|
||||
|
||||
#pragma warning disable CS0067
|
||||
public event VisibilityChangedEventHandler? VisibilityChanged;
|
||||
public event ActualApplicationThemeChangedEventHandler? ActualApplicationThemeChanged;
|
||||
#pragma warning restore CS0067
|
||||
|
||||
// Essential for plugins
|
||||
public void ChangeQuery(string query, bool requery = false) => _getMainViewModel().QueryText = query;
|
||||
public string GetTranslation(string key) => key;
|
||||
public List<PluginPair> GetAllPlugins() => PluginManager.AllPlugins;
|
||||
public MatchResult FuzzySearch(string query, string stringToCompare) =>
|
||||
Ioc.Default.GetRequiredService<StringMatcher>().FuzzyMatch(query, stringToCompare);
|
||||
|
||||
// Logging
|
||||
public void LogDebug(string className, string message, [CallerMemberName] string methodName = "") => Log.Debug(className, message, methodName);
|
||||
public void LogInfo(string className, string message, [CallerMemberName] string methodName = "") => Log.Info(className, message, methodName);
|
||||
public void LogWarn(string className, string message, [CallerMemberName] string methodName = "") => Log.Warn(className, message, methodName);
|
||||
public void LogError(string className, string message, [CallerMemberName] string methodName = "") => Log.Error(className, message, methodName);
|
||||
public void LogException(string className, string message, Exception e, [CallerMemberName] string methodName = "") => Log.Exception(className, message, e, methodName);
|
||||
|
||||
// Shell/URL operations
|
||||
public void ShellRun(string cmd, string filename = "cmd.exe") =>
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = filename, Arguments = $"/c {cmd}", UseShellExecute = true });
|
||||
public void OpenUrl(string url, bool? inPrivate = null) =>
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = url, UseShellExecute = true });
|
||||
public void OpenUrl(Uri url, bool? inPrivate = null) => OpenUrl(url.ToString(), inPrivate);
|
||||
public void OpenWebUrl(string url, bool? inPrivate = null) => OpenUrl(url, inPrivate);
|
||||
public void OpenWebUrl(Uri url, bool? inPrivate = null) => OpenUrl(url.ToString(), inPrivate);
|
||||
public void OpenAppUri(Uri appUri) => OpenUrl(appUri);
|
||||
public void OpenAppUri(string appUri) => OpenUrl(appUri);
|
||||
public void OpenDirectory(string DirectoryPath, string? FileNameOrFilePath = null) =>
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = DirectoryPath, UseShellExecute = true });
|
||||
|
||||
// Clipboard
|
||||
public void CopyToClipboard(string text, bool directCopy = false, bool showDefaultNotification = true)
|
||||
{
|
||||
if (global::Avalonia.Application.Current?.ApplicationLifetime is global::Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop)
|
||||
desktop.MainWindow?.Clipboard?.SetTextAsync(text);
|
||||
}
|
||||
|
||||
// HTTP (delegate to Infrastructure)
|
||||
public Task<string> HttpGetStringAsync(string url, CancellationToken token = default) => Infrastructure.Http.Http.GetAsync(url, token);
|
||||
public Task<Stream> HttpGetStreamAsync(string url, CancellationToken token = default) => Infrastructure.Http.Http.GetStreamAsync(url, token);
|
||||
public Task HttpDownloadAsync(string url, string filePath, Action<double>? reportProgress = null, CancellationToken token = default) =>
|
||||
Infrastructure.Http.Http.DownloadAsync(url, filePath, reportProgress, token);
|
||||
|
||||
// Plugin management
|
||||
public void AddActionKeyword(string pluginId, string newActionKeyword) => PluginManager.AddActionKeyword(pluginId, newActionKeyword);
|
||||
public void RemoveActionKeyword(string pluginId, string oldActionKeyword) => PluginManager.RemoveActionKeyword(pluginId, oldActionKeyword);
|
||||
public bool ActionKeywordAssigned(string actionKeyword) => PluginManager.ActionKeywordRegistered(actionKeyword);
|
||||
public bool PluginModified(string id) => PluginManager.PluginModified(id);
|
||||
|
||||
// Paths
|
||||
public string GetDataDirectory() => DataLocation.DataDirectory();
|
||||
public string GetLogDirectory() => Log.CurrentLogDirectory;
|
||||
|
||||
// Stubs - not critical for basic queries
|
||||
public void RestartApp() { }
|
||||
public void SaveAppAllSettings() { }
|
||||
public void SavePluginSettings() { }
|
||||
public Task ReloadAllPluginData() => Task.CompletedTask;
|
||||
public void CheckForNewUpdate() { }
|
||||
public void ShowMsgError(string title, string subTitle = "") => LogError("API", $"{title}: {subTitle}");
|
||||
public void ShowMsgErrorWithButton(string title, string buttonText, Action buttonAction, string subTitle = "") { }
|
||||
public void ShowMainWindow() { }
|
||||
public void FocusQueryTextBox() { }
|
||||
public void HideMainWindow() => _getMainViewModel()?.RequestHide();
|
||||
public bool IsMainWindowVisible() => true;
|
||||
public void ShowMsg(string title, string subTitle = "", string iconPath = "") { }
|
||||
public void ShowMsg(string title, string subTitle, string iconPath, bool useMainWindowAsOwner = true) { }
|
||||
public void ShowMsgWithButton(string title, string buttonText, Action buttonAction, string subTitle = "", string iconPath = "") { }
|
||||
public void ShowMsgWithButton(string title, string buttonText, Action buttonAction, string subTitle, string iconPath, bool useMainWindowAsOwner = true) { }
|
||||
public void OpenSettingDialog() { }
|
||||
public void RegisterGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback) { }
|
||||
public void RemoveGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback) { }
|
||||
public T LoadSettingJsonStorage<T>() where T : new() => new T();
|
||||
public void SaveSettingJsonStorage<T>() where T : new() { }
|
||||
public void ToggleGameMode() { }
|
||||
public void SetGameMode(bool value) { }
|
||||
public bool IsGameModeOn() => false;
|
||||
public void ReQuery(bool reselect = true) { var q = _getMainViewModel().QueryText; _getMainViewModel().QueryText = ""; _getMainViewModel().QueryText = q; }
|
||||
public void BackToQueryResults() { }
|
||||
public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.OK) => defaultResult;
|
||||
public Task ShowProgressBoxAsync(string caption, Func<Action<double>, Task> reportProgressAsync, Action? cancelProgress = null) => reportProgressAsync(_ => { });
|
||||
public void StartLoadingBar() => _getMainViewModel().IsQueryRunning = true;
|
||||
public void StopLoadingBar() => _getMainViewModel().IsQueryRunning = false;
|
||||
public List<ThemeData> GetAvailableThemes() => new();
|
||||
public ThemeData? GetCurrentTheme() => null;
|
||||
public bool SetCurrentTheme(ThemeData theme) => false;
|
||||
public void SavePluginCaches() { }
|
||||
public Task<T> LoadCacheBinaryStorageAsync<T>(string cacheName, string cacheDirectory, T defaultData) where T : new() => Task.FromResult(defaultData);
|
||||
public Task SaveCacheBinaryStorageAsync<T>(string cacheName, string cacheDirectory) where T : new() => Task.CompletedTask;
|
||||
public ValueTask<ImageSource> LoadImageAsync(string path, bool loadFullImage = false, bool cacheImage = true) => new((ImageSource)null!);
|
||||
public Task<bool> UpdatePluginManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default) => Task.FromResult(true);
|
||||
public IReadOnlyList<UserPlugin> GetPluginManifest() => new List<UserPlugin>();
|
||||
public Task<bool> UpdatePluginAsync(PluginMetadata pluginMetadata, UserPlugin plugin, string zipFilePath) => Task.FromResult(false);
|
||||
public bool InstallPlugin(UserPlugin plugin, string zipFilePath) => false;
|
||||
public Task<bool> UninstallPluginAsync(PluginMetadata pluginMetadata, bool removePluginSettings = false) => Task.FromResult(false);
|
||||
public bool IsApplicationDarkTheme() => true;
|
||||
|
||||
public long StopwatchLogDebug(string className, string message, Action action, [CallerMemberName] string methodName = "")
|
||||
{ var sw = System.Diagnostics.Stopwatch.StartNew(); action(); sw.Stop(); LogDebug(className, $"{message}: {sw.ElapsedMilliseconds}ms", methodName); return sw.ElapsedMilliseconds; }
|
||||
public async Task<long> StopwatchLogDebugAsync(string className, string message, Func<Task> action, [CallerMemberName] string methodName = "")
|
||||
{ var sw = System.Diagnostics.Stopwatch.StartNew(); await action(); sw.Stop(); LogDebug(className, $"{message}: {sw.ElapsedMilliseconds}ms", methodName); return sw.ElapsedMilliseconds; }
|
||||
public long StopwatchLogInfo(string className, string message, Action action, [CallerMemberName] string methodName = "")
|
||||
{ var sw = System.Diagnostics.Stopwatch.StartNew(); action(); sw.Stop(); LogInfo(className, $"{message}: {sw.ElapsedMilliseconds}ms", methodName); return sw.ElapsedMilliseconds; }
|
||||
public async Task<long> StopwatchLogInfoAsync(string className, string message, Func<Task> action, [CallerMemberName] string methodName = "")
|
||||
{ var sw = System.Diagnostics.Stopwatch.StartNew(); await action(); sw.Stop(); LogInfo(className, $"{message}: {sw.ElapsedMilliseconds}ms", methodName); return sw.ElapsedMilliseconds; }
|
||||
}
|
||||
|
|
@ -49,6 +49,11 @@
|
|||
<ProjectReference Include="..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- WPF assemblies needed for IPublicAPI compatibility (ImageSource, MessageBox types) -->
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.WindowsDesktop.App.WPF" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Copy themes and languages from WPF project -->
|
||||
<ItemGroup>
|
||||
<Content Include="..\Flow.Launcher\Languages\*.xaml" Link="Languages\%(Filename)%(Extension)">
|
||||
|
|
|
|||
|
|
@ -31,16 +31,9 @@
|
|||
|
||||
<Window.KeyBindings>
|
||||
<KeyBinding Gesture="Escape" Command="{Binding EscCommand}" />
|
||||
<KeyBinding Gesture="F5" Command="{Binding ReloadPluginDataCommand}" />
|
||||
<KeyBinding Gesture="Enter" Command="{Binding OpenResultCommand}" />
|
||||
<KeyBinding Gesture="Ctrl+Enter" Command="{Binding OpenResultCommand}" />
|
||||
<KeyBinding Gesture="Alt+Enter" Command="{Binding OpenResultCommand}" />
|
||||
<KeyBinding Gesture="Down" Command="{Binding SelectNextItemCommand}" />
|
||||
<KeyBinding Gesture="Up" Command="{Binding SelectPrevItemCommand}" />
|
||||
<KeyBinding Gesture="Ctrl+N" Command="{Binding SelectNextItemCommand}" />
|
||||
<KeyBinding Gesture="Ctrl+P" Command="{Binding SelectPrevItemCommand}" />
|
||||
<KeyBinding Gesture="Tab" Command="{Binding AutocompleteQueryCommand}" />
|
||||
<KeyBinding Gesture="Ctrl+R" Command="{Binding ReQueryCommand}" />
|
||||
</Window.KeyBindings>
|
||||
|
||||
<!-- Main container with rounded corners and background -->
|
||||
|
|
@ -52,14 +45,6 @@
|
|||
<!-- Query Box Area -->
|
||||
<Grid Name="QueryBoxArea">
|
||||
<Border Name="QueryBoxBg" Classes="queryBoxBg" MinHeight="30">
|
||||
<Grid>
|
||||
<!-- Query Suggestion Box (autocomplete hint) -->
|
||||
<TextBox Name="QueryTextSuggestionBox"
|
||||
Classes="querySuggestionBox"
|
||||
IsEnabled="False"
|
||||
IsHitTestVisible="False"
|
||||
Text="{Binding QuerySuggestionText, Mode=OneWay}" />
|
||||
|
||||
<!-- Main Query Text Box -->
|
||||
<TextBox Name="QueryTextBox"
|
||||
Classes="queryBox"
|
||||
|
|
@ -67,7 +52,6 @@
|
|||
Watermark="Type to search..."
|
||||
AcceptsReturn="False"
|
||||
TextWrapping="NoWrap" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Search Icon -->
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ public partial class MainWindow : Window
|
|||
// Create and set the ViewModel
|
||||
var settings = Ioc.Default.GetRequiredService<Settings>();
|
||||
_viewModel = new MainViewModel(settings);
|
||||
_viewModel.HideRequested += () => Hide();
|
||||
DataContext = _viewModel;
|
||||
|
||||
// Get reference to the query text box
|
||||
|
|
|
|||
|
|
@ -1,27 +1,32 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Flow.Launcher.Core.Plugin;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
namespace Flow.Launcher.Avalonia.ViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Simplified MainViewModel for the Avalonia version.
|
||||
/// This will eventually be unified with the WPF MainViewModel.
|
||||
/// MainViewModel for Avalonia - minimal implementation for plugin queries.
|
||||
/// </summary>
|
||||
public partial class MainViewModel : ObservableObject
|
||||
{
|
||||
private static readonly string ClassName = nameof(MainViewModel);
|
||||
private readonly Settings _settings;
|
||||
private CancellationTokenSource? _queryTokenSource;
|
||||
private bool _pluginsReady;
|
||||
|
||||
public event Action? HideRequested;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _queryText = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _querySuggestionText = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isQueryRunning;
|
||||
|
||||
|
|
@ -37,115 +42,107 @@ public partial class MainViewModel : ObservableObject
|
|||
{
|
||||
_settings = settings;
|
||||
_results = new ResultsViewModel(settings);
|
||||
|
||||
// Add some demo results for testing
|
||||
AddDemoResults();
|
||||
}
|
||||
|
||||
partial void OnQueryTextChanged(string value)
|
||||
public void OnPluginsReady()
|
||||
{
|
||||
// Simulate query execution
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
IsQueryRunning = true;
|
||||
HasResults = true;
|
||||
|
||||
// Simulate search
|
||||
Task.Delay(100).ContinueWith(_ =>
|
||||
{
|
||||
IsQueryRunning = false;
|
||||
}, TaskScheduler.FromCurrentSynchronizationContext());
|
||||
}
|
||||
else
|
||||
_pluginsReady = true;
|
||||
Log.Info(ClassName, "Plugins ready");
|
||||
if (!string.IsNullOrWhiteSpace(QueryText))
|
||||
_ = QueryAsync();
|
||||
}
|
||||
|
||||
public void RequestHide() => HideRequested?.Invoke();
|
||||
|
||||
partial void OnQueryTextChanged(string value) => _ = QueryAsync();
|
||||
|
||||
private async Task QueryAsync()
|
||||
{
|
||||
_queryTokenSource?.Cancel();
|
||||
_queryTokenSource = new CancellationTokenSource();
|
||||
var token = _queryTokenSource.Token;
|
||||
var queryText = QueryText.Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(queryText) || !_pluginsReady)
|
||||
{
|
||||
Results.Clear();
|
||||
HasResults = false;
|
||||
IsQueryRunning = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddDemoResults()
|
||||
{
|
||||
// Add demo results for UI testing
|
||||
Results.AddResult(new ResultViewModel
|
||||
{
|
||||
Title = "Welcome to Flow Launcher (Avalonia)",
|
||||
SubTitle = "This is a demo result - Avalonia migration in progress",
|
||||
IconPath = "Images/app.png"
|
||||
});
|
||||
|
||||
Results.AddResult(new ResultViewModel
|
||||
{
|
||||
Title = "Settings",
|
||||
SubTitle = "Open Flow Launcher settings",
|
||||
IconPath = "Images/app.png"
|
||||
});
|
||||
|
||||
Results.AddResult(new ResultViewModel
|
||||
{
|
||||
Title = "Notepad",
|
||||
SubTitle = "C:\\Windows\\System32\\notepad.exe",
|
||||
IconPath = "Images/app.png"
|
||||
});
|
||||
IsQueryRunning = true;
|
||||
|
||||
Results.AddResult(new ResultViewModel
|
||||
try
|
||||
{
|
||||
Title = "Calculator",
|
||||
SubTitle = "Microsoft Calculator",
|
||||
IconPath = "Images/app.png"
|
||||
});
|
||||
var query = QueryBuilder.Build(queryText, PluginManager.NonGlobalPlugins);
|
||||
if (query == null) { Results.Clear(); HasResults = false; return; }
|
||||
|
||||
HasResults = true;
|
||||
}
|
||||
var plugins = PluginManager.ValidPluginsForQuery(query, dialogJump: false)
|
||||
.Where(p => !p.Metadata.Disabled).ToList();
|
||||
|
||||
[RelayCommand]
|
||||
private void Esc()
|
||||
{
|
||||
QueryText = string.Empty;
|
||||
}
|
||||
if (plugins.Count == 0) { Results.Clear(); HasResults = false; return; }
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenResult(object? parameter)
|
||||
{
|
||||
var selectedResult = Results.SelectedItem;
|
||||
if (selectedResult != null)
|
||||
{
|
||||
// Execute the result action
|
||||
System.Diagnostics.Debug.WriteLine($"Opening result: {selectedResult.Title}");
|
||||
Results.Clear();
|
||||
|
||||
var tasks = plugins.Select(p => QueryPluginAsync(p, query, token));
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
if (!token.IsCancellationRequested)
|
||||
HasResults = Results.Results.Count > 0;
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch (Exception e) { Log.Exception(ClassName, "Query error", e); }
|
||||
finally { if (!token.IsCancellationRequested) IsQueryRunning = false; }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SelectNextItem()
|
||||
private async Task QueryPluginAsync(PluginPair plugin, Query query, CancellationToken token)
|
||||
{
|
||||
Results.SelectNextItem();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SelectPrevItem()
|
||||
{
|
||||
Results.SelectPrevItem();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void AutocompleteQuery()
|
||||
{
|
||||
if (Results.SelectedItem != null)
|
||||
try
|
||||
{
|
||||
QueryText = Results.SelectedItem.Title;
|
||||
var delay = plugin.Metadata.SearchDelayTime ?? _settings.SearchDelayTime;
|
||||
if (delay > 0) await Task.Delay(delay, token);
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
var results = await PluginManager.QueryForPluginAsync(plugin, query, token);
|
||||
if (token.IsCancellationRequested || results == null || results.Count == 0) return;
|
||||
|
||||
await global::Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
foreach (var r in results.OrderByDescending(r => r.Score).Take(_settings.MaxResultsToShow))
|
||||
{
|
||||
if (token.IsCancellationRequested) return;
|
||||
Results.AddResult(new ResultViewModel
|
||||
{
|
||||
Title = r.Title ?? "",
|
||||
SubTitle = r.SubTitle ?? "",
|
||||
IconPath = r.IcoPath ?? plugin.Metadata.IcoPath ?? "",
|
||||
PluginResult = r
|
||||
});
|
||||
}
|
||||
HasResults = Results.Results.Count > 0;
|
||||
});
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch (Exception e) { Log.Exception(ClassName, $"Plugin {plugin.Metadata.Name} error", e); }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ReloadPluginData()
|
||||
{
|
||||
// Placeholder for plugin data reload
|
||||
System.Diagnostics.Debug.WriteLine("Reloading plugin data...");
|
||||
}
|
||||
private void Esc() { QueryText = ""; HideRequested?.Invoke(); }
|
||||
|
||||
[RelayCommand]
|
||||
private void ReQuery()
|
||||
private async Task OpenResultAsync()
|
||||
{
|
||||
// Placeholder for re-query
|
||||
System.Diagnostics.Debug.WriteLine("Re-querying...");
|
||||
var result = Results.SelectedItem?.PluginResult;
|
||||
if (result == null) return;
|
||||
try
|
||||
{
|
||||
if (await result.ExecuteAsync(new ActionContext { SpecialKeyState = SpecialKeyState.Default }))
|
||||
HideRequested?.Invoke();
|
||||
}
|
||||
catch (Exception e) { Log.Exception(ClassName, "Execute error", e); }
|
||||
}
|
||||
|
||||
[RelayCommand] private void SelectNextItem() => Results.SelectNextItem();
|
||||
[RelayCommand] private void SelectPrevItem() => Results.SelectPrevItem();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
namespace Flow.Launcher.Avalonia.ViewModel;
|
||||
|
||||
|
|
@ -23,8 +24,18 @@ public partial class ResultViewModel : ObservableObject
|
|||
[ObservableProperty]
|
||||
private Settings? _settings;
|
||||
|
||||
/// <summary>
|
||||
/// The underlying plugin result. Used for executing actions and accessing additional properties.
|
||||
/// </summary>
|
||||
public Result? PluginResult { get; set; }
|
||||
|
||||
// Computed properties for display
|
||||
public bool ShowIcon => !string.IsNullOrEmpty(IconPath);
|
||||
|
||||
public bool ShowSubTitle => !string.IsNullOrEmpty(SubTitle);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the query suggestion text for autocomplete, if available.
|
||||
/// </summary>
|
||||
public string? QuerySuggestionText => PluginResult?.AutoCompleteText;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue